feat: 初始化项目并完成基础功能开发

- 完成项目初始化
- 实现用户注册、登录功能
- 完成用户管理与权限管理模块
- 开发后端 Tracker 服务器项目管理接口
- 实现日志管理接口
Change-Id: Ia4bde1c9ff600352a7ff0caca0cc50b02cad1af7
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..14ca66d
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: https://gitee.com/whiteshader/ruoyi-react
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ed8368a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,47 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+*.swp
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..3541dcd
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,10 @@
+{
+    "files.exclude": {
+        "**/.classpath": true,
+        "**/.project": true,
+        "**/.settings": true,
+        "**/.factorypath": true
+    },
+    "java.compile.nullAnalysis.mode": "automatic",
+    "java.configuration.updateBuildConfiguration": "interactive"
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8564f29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 RuoYi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..788d9ac
--- /dev/null
+++ b/README.md
@@ -0,0 +1,138 @@
+## 平台简介
+
+项目链接:https://gitee.com/whiteshader/ruoyi-react
+
+微服务分支:https://gitee.com/whiteshader/ruoyi-react/tree/spring-cloud-v3/
+
+若依(Ruoyi-React)是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
+
+* 前端采用React 18、Ant Design Pro 6、TypeScript 5。
+* 后端采用JDK17, Spring Boot v3、Spring Security、Redis & Jwt。
+* 权限认证使用Jwt,支持多终端认证系统。
+* 支持加载动态权限菜单,多方式轻松权限控制。
+* 高效率开发,使用代码生成器可以一键生成前后端代码。
+
+
+## 内置功能
+
+1.  用户管理:用户是系统操作者,该功能主要完成系统用户配置。
+2.  部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
+3.  岗位管理:配置系统用户所属担任职务。
+4.  菜单管理:配置系统菜单,操作权限,按钮权限标识等。
+5.  角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
+6.  字典管理:对系统中经常使用的一些较为固定的数据进行维护。
+7.  参数管理:对系统动态配置常用参数。
+8.  通知公告:系统通知公告信息发布维护。
+9.  操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
+10. 登录日志:系统登录日志记录查询包含登录异常。
+11. 在线用户:当前系统中活跃用户状态监控。
+12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
+13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
+14. 系统接口:根据业务代码自动生成相关的api接口文档。
+15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
+16. 在线构建器:拖动表单元素生成相应的HTML代码。
+17. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
+
+## 在线体验
+
+- admin/admin123  
+- ry/123456
+
+演示地址:暂时没有
+ 
+https://gitee.com/whiteshader/ruoyi-cloud-vben/blob/master/ruoyi-react-demo-2023-04-27.gif
+
+## 前端开发注意事项
+
+Node:建议v16或以上
+
+安装依赖请支行:npm i
+
+正常启动请运行: npm run dev
+
+Mock测试模式请运行: npm run start
+
+发布打包请运行: npm run build
+
+## 相关技术文档
+
+### 后端说明文档
+http://doc.ruoyi.vip/ruoyi-cloud/
+
+### TypeScript
+https://www.tslang.cn/docs/home.html
+
+### React Js
+https://react.docschina.org/docs/getting-started.html
+
+### Ant Design 
+https://ant.design/components/overview-cn/
+
+### Ant Design Pro
+https://pro.ant.design/zh-CN/docs/overview
+
+### Ant Design Chart
+https://charts.ant.design/zh
+
+### Umi Js
+https://umijs.org/docs/introduce/introduce
+
+## 部署
+http://doc.ruoyi.vip/ruoyi-vue/document/hjbs.html#nginx%E9%85%8D%E7%BD%AE
+
+## 演示图
+
+<table>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-9996b274886e8134066ccee096fde2089dd.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-66afe06885d34482862536e4f00c87c0475.png"/></td>
+    </tr>    
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-f279ee4e419e9ba80a77fd898ebd8c9ac45.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-b56c891e29d1dfd0213b000339effd256db.png"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-26d4a0f56967f4c319d6e95cab9652bdbfe.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-125aed48a8214551cb2ce5aa5a1403d78e9.png"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-59bc1efe5d8f109e56305aa86192ff56bb0.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-6e081044a6f864c96df9a25aaa26516f7fc.png"/></td>
+    </tr>
+	<tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-ed2e67f41c8a56e0db1215645a0d9dd1e52.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-2788241f7893ac8fbfd2b84813f60451755.png"/></td>
+    </tr>
+	<tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-eda1770f6383e0001439b56c3392012213d.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-31c487d7419b16bc79de0d6a6a12789f048.png"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-31c487d7419b16bc79de0d6a6a12789f048.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-4d8cd86ba198f0263f90a0bd36c47b0317b.png"/></td>
+    </tr>
+	<tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-6d0ba703a00f8b02a0540931c9e67fe816c.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-376159966aa67e7e2fdd971bf68fb0a3375.png"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-77b186361c754bd9abc6beac7b2dd371858.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-800aba850793feb11e52720153a801cc2e5.png"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-8835cf289be21d9ed81974764670d78d120.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-31a968948be45abb0a30bd7b69fd9bee501.png"/></td>
+    </tr>
+    <tr>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-ed8b654a35b70d5b14281c7d5f086658e27.png"/></td>
+        <td><img src="https://oscimg.oschina.net/oscnet/up-e7f3e329aa2052d32f64a372f25ad9f5df1.png"/></td>
+    </tr>
+</table>
+
+
+## 若依(Ruoyi-React)前后端分离交流群
+
+QQ群: [![加入QQ群](https://img.shields.io/badge/201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=u58VEEQK) 点击按钮入群。
+
+
+## 感谢捐赠的伙伴,谢谢你们的支持
\ No newline at end of file
diff --git a/bin/clean.bat b/bin/clean.bat
new file mode 100644
index 0000000..24c0974
--- /dev/null
+++ b/bin/clean.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [ÐÅÏ¢] ÇåÀí¹¤³ÌtargetÉú³É·¾¶¡£
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean
+
+pause
\ No newline at end of file
diff --git a/bin/package.bat b/bin/package.bat
new file mode 100644
index 0000000..c693ec0
--- /dev/null
+++ b/bin/package.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [ÐÅÏ¢] ´ò°üWeb¹¤³Ì£¬Éú³Éwar/jar°üÎļþ¡£
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean package -Dmaven.test.skip=true
+
+pause
\ No newline at end of file
diff --git a/bin/run.bat b/bin/run.bat
new file mode 100644
index 0000000..41efbd0
--- /dev/null
+++ b/bin/run.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [ÐÅÏ¢] ʹÓÃJarÃüÁîÔËÐÐWeb¹¤³Ì¡£
+echo.
+
+cd %~dp0
+cd ../ruoyi-admin/target
+
+set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -jar %JAVA_OPTS% ruoyi-admin.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git "a/doc/\350\213\245\344\276\235\347\216\257\345\242\203\344\275\277\347\224\250\346\211\213\345\206\214.docx" "b/doc/\350\213\245\344\276\235\347\216\257\345\242\203\344\275\277\347\224\250\346\211\213\345\206\214.docx"
new file mode 100644
index 0000000..9e4daef
--- /dev/null
+++ "b/doc/\350\213\245\344\276\235\347\216\257\345\242\203\344\275\277\347\224\250\346\211\213\345\206\214.docx"
Binary files differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..843a88f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	
+    <groupId>com.ruoyi</groupId>
+    <artifactId>ruoyi</artifactId>
+    <version>3.8.8</version>
+
+    <name>ruoyi</name>
+    <url>http://www.ruoyi.vip</url>
+    <description>若依管理系统</description>
+    
+    <properties>
+        <ruoyi.version>3.8.8</ruoyi.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>17</java.version>
+        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
+        <mybatis-spring-boot.version>3.0.3</mybatis-spring-boot.version>
+        <druid.version>1.2.23</druid.version>
+        <bitwalker.version>1.21</bitwalker.version>
+        <swagger.version>3.0.0</swagger.version>
+        <kaptcha.version>2.3.3</kaptcha.version>
+        <pagehelper.boot.version>2.1.0</pagehelper.boot.version>
+        <fastjson.version>2.0.43</fastjson.version>
+        <oshi.version>6.6.3</oshi.version>
+        <commons.io.version>2.13.0</commons.io.version>
+        <poi.version>4.1.2</poi.version>
+        <velocity.version>2.3</velocity.version>
+        <jwt.version>0.9.1</jwt.version>
+        <mysql.version>8.2.0</mysql.version>
+        <jaxb-api.version>2.3.1</jaxb-api.version>
+        <jakarta.version>6.0.0</jakarta.version>
+        <springdoc.version>2.5.0</springdoc.version>
+    </properties>
+
+    <!-- 依赖声明 -->
+    <dependencyManagement>
+        <dependencies>
+
+            <!-- SpringBoot的依赖配置-->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>3.3.0</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- 阿里数据库连接池 -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-3-starter</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+
+            <!-- 解析客户端操作系统、浏览器等 -->
+            <dependency>
+                <groupId>eu.bitwalker</groupId>
+                <artifactId>UserAgentUtils</artifactId>
+                <version>${bitwalker.version}</version>
+            </dependency>
+
+            <!-- pagehelper 分页插件 -->
+            <dependency>
+                <groupId>com.github.pagehelper</groupId>
+                <artifactId>pagehelper-spring-boot-starter</artifactId>
+                <version>${pagehelper.boot.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.mybatis.spring.boot</groupId>
+                <artifactId>mybatis-spring-boot-starter</artifactId>
+                <version>${mybatis-spring-boot.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.mysql</groupId>
+                <artifactId>mysql-connector-j</artifactId>
+                <version>${mysql.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>javax.xml.bind</groupId>
+                <artifactId>jaxb-api</artifactId>
+                <version>${jaxb-api.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>jakarta.servlet</groupId>
+                <artifactId>jakarta.servlet-api</artifactId>
+                <version>${jakarta.version}</version>
+            </dependency>
+
+            <!-- 获取系统信息 -->
+            <dependency>
+                <groupId>com.github.oshi</groupId>
+                <artifactId>oshi-core</artifactId>
+                <version>${oshi.version}</version>
+            </dependency>
+
+            <!-- spring-doc -->
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <!-- io常用工具类 -->
+            <dependency>
+                <groupId>commons-io</groupId>
+                <artifactId>commons-io</artifactId>
+                <version>${commons.io.version}</version>
+            </dependency>
+
+            <!-- excel工具 -->
+            <dependency>
+                <groupId>org.apache.poi</groupId>
+                <artifactId>poi-ooxml</artifactId>
+                <version>${poi.version}</version>
+            </dependency>
+
+            <!-- velocity代码生成使用模板 -->
+            <dependency>
+                <groupId>org.apache.velocity</groupId>
+                <artifactId>velocity-engine-core</artifactId>
+                <version>${velocity.version}</version>
+            </dependency>
+
+            <!-- 阿里JSON解析器 -->
+            <dependency>
+                <groupId>com.alibaba.fastjson2</groupId>
+                <artifactId>fastjson2</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
+            <!-- Token生成与解析-->
+            <dependency>
+                <groupId>io.jsonwebtoken</groupId>
+                <artifactId>jjwt</artifactId>
+                <version>${jwt.version}</version>
+            </dependency>
+
+            <!-- 验证码 -->
+            <dependency>
+                <groupId>pro.fessional</groupId>
+                <artifactId>kaptcha</artifactId>
+                <version>${kaptcha.version}</version>
+            </dependency>
+
+            <!-- 定时任务-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-quartz</artifactId>
+                <version>${ruoyi.version}</version>
+            </dependency>
+
+            <!-- 代码生成-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-generator</artifactId>
+                <version>${ruoyi.version}</version>
+            </dependency>
+
+            <!-- 核心模块-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-framework</artifactId>
+                <version>${ruoyi.version}</version>
+            </dependency>
+
+            <!-- 系统模块-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-system</artifactId>
+                <version>${ruoyi.version}</version>
+            </dependency>
+
+            <!-- 通用工具-->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-common</artifactId>
+                <version>${ruoyi.version}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+
+    <modules>
+        <module>ruoyi-admin</module>
+        <module>ruoyi-framework</module>
+        <module>ruoyi-system</module>
+        <module>ruoyi-quartz</module>
+        <module>ruoyi-generator</module>
+        <module>ruoyi-common</module>
+    </modules>
+    <packaging>pom</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.13.0</version>
+                <configuration>
+                    <parameters>true</parameters>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>3.3.0</version>
+            </plugin>
+        </plugins>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+
+</project>
\ No newline at end of file
diff --git a/react-ui/.editorconfig b/react-ui/.editorconfig
new file mode 100644
index 0000000..7e3649a
--- /dev/null
+++ b/react-ui/.editorconfig
@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab
diff --git a/react-ui/.eslintignore b/react-ui/.eslintignore
new file mode 100644
index 0000000..8336e93
--- /dev/null
+++ b/react-ui/.eslintignore
@@ -0,0 +1,8 @@
+/lambda/
+/scripts
+/config
+.history
+public
+dist
+.umi
+mock
\ No newline at end of file
diff --git a/react-ui/.eslintrc.js b/react-ui/.eslintrc.js
new file mode 100644
index 0000000..3ac39ef
--- /dev/null
+++ b/react-ui/.eslintrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+  extends: [require.resolve('@umijs/lint/dist/config/eslint')],
+  globals: {
+    page: true,
+    REACT_APP_ENV: true,
+  },
+};
diff --git a/react-ui/.github/FUNDING.yml b/react-ui/.github/FUNDING.yml
new file mode 100644
index 0000000..26b00e2
--- /dev/null
+++ b/react-ui/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: ant-design
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/react-ui/.github/ISSUE_TEMPLATE/bug_report.md b/react-ui/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..5cb2692
--- /dev/null
+++ b/react-ui/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,48 @@
+---
+name: '报告 Bug | Report bug 🐛'
+about: 报告 Ant Design Pro 的 bug
+title: '🐛 [BUG]'
+labels: '🐛 bug'
+assignees: ''
+---
+
+### 🐛 bug 描述
+
+<!--
+详细地描述 bug,让大家都能理解
+Describe the bug in detail so that everyone can understand it
+-->
+
+### 📷 复现步骤 | Recurrence steps
+
+<!--
+清晰描述复现步骤,让别人也能看到问题
+Clearly describe the recurrence steps so that others can see the problem
+-->
+
+### 🏞 期望结果 | Expected results
+
+<!--
+描述你原本期望看到的结果
+Describe what you expected to see
+-->
+
+### 💻 复现代码 | Recurrence code
+
+<!--
+提供可复现的代码,仓库,或线上示例
+Provide reproducible code, warehouse, or online examples
+-->
+
+### © 版本信息
+
+- Ant Design Pro 版本: [e.g. 4.0.0]
+- umi 版本
+- 浏览器环境
+- 开发环境 [e.g. mac OS]
+
+### 🚑 其他信息
+
+<!--
+如截图等其他信息可以贴在这里
+-->
diff --git a/react-ui/.github/ISSUE_TEMPLATE/feature_request.md b/react-ui/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..b2b866a
--- /dev/null
+++ b/react-ui/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,28 @@
+---
+name: '功能需求 | Feature Requirements ✨'
+about: 对 Ant Design Pro  的需求或建议
+title: '👑 [需求 | Feature]'
+labels: '👑 Feature Request'
+assignees: ''
+---
+
+### 🥰 需求描述 | Requirements description
+
+<!--
+详细地描述需求,让大家都能理解
+Describe the requirements in detail so that everyone can understand them
+-->
+
+### 🧐 解决方案 | Solution
+
+<!--
+如果你有解决方案,在这里清晰地阐述
+If you have a solution, explain it clearly here
+-->
+
+### 🚑 其他信息 | Other information
+
+<!--
+如截图等其他信息可以贴在这里
+Other information such as screenshots can be posted here
+-->
diff --git a/react-ui/.github/ISSUE_TEMPLATE/question.md b/react-ui/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000..f0fbe7d
--- /dev/null
+++ b/react-ui/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,34 @@
+---
+name: '疑问或需要帮助 | Questions or need help ❓'
+about: 对 Ant Design Pro 使用的疑问或需要帮助
+title: '🧐[问题 | question]'
+labels: '🧐 question'
+assignees: ''
+---
+
+### 🧐 问题描述 | Problem description
+
+<!--
+详细地描述问题,让大家都能理解
+Describe the problem in detail so that everyone can understand it
+-->
+
+### 💻 示例代码 | Sample code
+
+<!--
+一个最小可重现的代码,让开发者可以快速的定位问题
+A minimal reproducible code that allows developers to quickly locate problems
+-->
+
+### 🚑 其他信息 | Other information
+
+<!--
+如截图等其他信息可以贴在这里
+Other information such as screenshots can be posted here
+-->
+
+OS:
+
+Node:
+
+浏览器 | browser:
diff --git a/react-ui/.github/workflows/ci.yml b/react-ui/.github/workflows/ci.yml
new file mode 100644
index 0000000..d6f5693
--- /dev/null
+++ b/react-ui/.github/workflows/ci.yml
@@ -0,0 +1,30 @@
+name: Node CI
+
+on: [push, pull_request]
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        node_version: [16.x, 14.x]
+        os: [ubuntu-latest, windows-latest, macOS-latest]
+    steps:
+      - uses: actions/checkout@v1
+      - name: Use Node.js ${{ matrix.node_version }}
+        uses: actions/setup-node@v1
+        with:
+          node-version: ${{ matrix.node_version }}
+      - run: echo ${{github.ref}}
+      - run: npm install
+      - run: yarn run lint
+      - run: yarn run tsc
+      - run: yarn run build
+        env:
+          CI: true
+          PROGRESS: none
+          NODE_ENV: test
+          NODE_OPTIONS: --max_old_space_size=4096
diff --git a/react-ui/.github/workflows/codeql.yml b/react-ui/.github/workflows/codeql.yml
new file mode 100644
index 0000000..705b30e
--- /dev/null
+++ b/react-ui/.github/workflows/codeql.yml
@@ -0,0 +1,41 @@
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+  schedule:
+    - cron: "48 12 * * 2"
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ javascript ]
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@v2
+        with:
+          languages: ${{ matrix.language }}
+          queries: +security-and-quality
+
+      - name: Autobuild
+        uses: github/codeql-action/autobuild@v2
+
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@v2
+        with:
+          category: "/language:${{ matrix.language }}"
diff --git a/react-ui/.github/workflows/coverage.yml b/react-ui/.github/workflows/coverage.yml
new file mode 100644
index 0000000..d145753
--- /dev/null
+++ b/react-ui/.github/workflows/coverage.yml
@@ -0,0 +1,27 @@
+name: coverage CI
+
+on: [push, pull_request]
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: Use Node.js 16.x
+        uses: actions/setup-node@v1
+        with:
+          node-version: 16.x
+      - run: echo ${{github.ref}}
+      - run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
+      - run: pnpm config set store-dir ~/.pnpm-store
+      - run: pnpm install  --strict-peer-dependencies=false
+      - run: yarn run test:coverage
+        env:
+          CI: true
+          PROGRESS: none
+          NODE_ENV: test
+          NODE_OPTIONS: --max_old_space_size=4096
+      - run: bash <(curl -s https://codecov.io/bash)
diff --git a/react-ui/.github/workflows/emoji-helper.yml b/react-ui/.github/workflows/emoji-helper.yml
new file mode 100644
index 0000000..8965a1a
--- /dev/null
+++ b/react-ui/.github/workflows/emoji-helper.yml
@@ -0,0 +1,14 @@
+name: Emoji Helper
+
+on:
+  release:
+    types: [published]
+
+jobs:
+  emoji:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions-cool/emoji-helper@v1.0.0
+        with:
+          type: 'release'
+          emoji: '+1, laugh, heart, hooray, rocket, eyes'
diff --git a/react-ui/.github/workflows/issue-labeled.yml b/react-ui/.github/workflows/issue-labeled.yml
new file mode 100644
index 0000000..a3e8b5f
--- /dev/null
+++ b/react-ui/.github/workflows/issue-labeled.yml
@@ -0,0 +1,37 @@
+name: Issue labeled
+
+on:
+  issues:
+    types: [labeled]
+
+jobs:
+  reply-helper:
+    runs-on: ubuntu-latest
+    steps:
+      - name: help wanted
+        if: github.event.label.name == '❤️ help wanted' || github.event.label.name == '🤝Welcome PR'
+        uses: actions-cool/issues-helper@v1.11
+        with:
+          actions: 'create-comment'
+          token: ${{ secrets.GITHUB_TOKEN }}
+          issue-number: ${{ github.event.issue.number }}
+          body: |
+            Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome to [send us a Pull Request](https://help.github.com/en/articles/creating-a-pull-request) for it. Please provide changelog/TypeScript/documentation/test cases if needed and make sure CI passed, we will review it soon. We appreciate your effort in advance and looking forward to your contribution!
+
+            你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎直接在此仓库 [创建一个 Pull Request](https://help.github.com/en/articles/creating-a-pull-request) 来解决这个问题。请务必提供改动所需相应的 changelog、TypeScript 定义、测试用例、文档等,并确保 CI 通过,我们会尽快进行 Review,提前感谢和期待您的贡献。
+
+            ![giphy](https://user-images.githubusercontent.com/507615/62342668-4735dc00-b51a-11e9-92a7-d46fbb1cc0c7.gif)
+
+      - name: Need Reproduce
+        if: github.event.label.name == '🤔 Need Reproduce'
+        uses: actions-cool/issues-helper@v1.11
+        with:
+          actions: 'create-comment'
+          token: ${{ secrets.GITHUB_TOKEN }}
+          issue-number: ${{ github.event.issue.number }}
+          body: |
+            Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by forking this link https://codesandbox.io/ or a minimal GitHub repository.
+
+            你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处](https://codesandbox.io/) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。
+
+            ![](https://gw.alipayobjects.com/zos/antfincdn/y9kwg7DVCd/reproduce.gif)
diff --git a/react-ui/.github/workflows/issue-open-check.yml b/react-ui/.github/workflows/issue-open-check.yml
new file mode 100644
index 0000000..3644213
--- /dev/null
+++ b/react-ui/.github/workflows/issue-open-check.yml
@@ -0,0 +1,34 @@
+name: Issue Open Check
+
+on:
+  issues:
+    types: [opened, edited]
+
+jobs:
+  check-issue:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions-cool/issues-helper@v2.2.0
+        id: check
+        with:
+          actions: 'check-issue'
+          issue-number: ${{ github.event.issue.number }}
+          title-excludes: '🐛 [BUG], 👑 [需求 | Feature], 🧐[问题 | question]'
+
+      - if: steps.check.outputs.check-result == 'false' && github.event.issue.state == 'open'
+        uses: actions-cool/issues-helper@v2.2.0
+        with:
+          actions: 'create-comment, close-issue'
+          issue-number: ${{ github.event.issue.number }}
+          body: |
+            当前 Issue 未检测到标题,请规范填写,谢谢!
+
+            The title of the current issue is not detected, please fill in according to the specifications, thank you!
+
+      - if: steps.check.outputs.check-result == 'true'
+        uses: actions-cool/issues-similarity-analysis@v1
+        with:
+          filter-threshold: 0.8
+          title-excludes: '🐛[BUG], 👑 [需求 | Feature], 🧐[问题 | question]'
+          comment-title: '### 以下的 Issues 可能会帮助到你 / The following issues may help you'
+          show-footer: false
diff --git a/react-ui/.github/workflows/pnpm.yml b/react-ui/.github/workflows/pnpm.yml
new file mode 100644
index 0000000..63bb2e4
--- /dev/null
+++ b/react-ui/.github/workflows/pnpm.yml
@@ -0,0 +1,33 @@
+name: Node pnpm CI
+
+on: [push, pull_request]
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        node_version: [16.x]
+        os: [ubuntu-latest, windows-latest, macOS-latest]
+    steps:
+      - uses: actions/checkout@v1
+      - name: Use Node.js ${{ matrix.node_version }}
+        uses: actions/setup-node@v1
+        with:
+          node-version: ${{ matrix.node_version }}
+      - run: echo ${{github.ref}}
+      - run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
+      - run: pnpm config set store-dir ~/.pnpm-store
+      - run: pnpm install  --strict-peer-dependencies=false
+      - run: pnpm run lint
+      - run: pnpm run tsc
+      - run: pnpm run build
+      - run: pnpm run test
+        env:
+          CI: true
+          PROGRESS: none
+          NODE_ENV: test
+          NODE_OPTIONS: --max_old_space_size=4096
diff --git a/react-ui/.github/workflows/preview-build.yml b/react-ui/.github/workflows/preview-build.yml
new file mode 100644
index 0000000..d8667a5
--- /dev/null
+++ b/react-ui/.github/workflows/preview-build.yml
@@ -0,0 +1,41 @@
+name: Preview Build
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened]
+
+permissions:
+  contents: read
+
+jobs:
+  build-preview:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          ref: ${{ github.event.pull_request.head.sha }}
+
+      - name: build
+        run: |
+          yarn
+          yarn add umi-plugin-pro --save
+          yarn build
+
+      - name: upload dist artifact
+        uses: actions/upload-artifact@v2
+        with:
+          name: dist
+          path: dist/
+          retention-days: 5
+
+      - name: Save PR number
+        if: ${{ always() }}
+        run: echo ${{ github.event.number }} > ./pr-id.txt
+
+      - name: Upload PR number
+        if: ${{ always() }}
+        uses: actions/upload-artifact@v2
+        with:
+          name: pr
+          path: ./pr-id.txt
diff --git a/react-ui/.github/workflows/preview-deploy.yml b/react-ui/.github/workflows/preview-deploy.yml
new file mode 100644
index 0000000..e9a699f
--- /dev/null
+++ b/react-ui/.github/workflows/preview-deploy.yml
@@ -0,0 +1,100 @@
+name: Preview Deploy
+
+on:
+  workflow_run:
+    workflows: ['Preview Build']
+    types:
+      - completed
+
+permissions:
+  contents: read
+
+jobs:
+  success:
+    permissions:
+      actions: read # for dawidd6/action-download-artifact to query and download artifacts
+      issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
+      pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
+    runs-on: ubuntu-latest
+    if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
+    steps:
+      - name: download pr artifact
+        uses: dawidd6/action-download-artifact@v2
+        with:
+          workflow: ${{ github.event.workflow_run.workflow_id }}
+          name: pr
+
+      - name: save PR id
+        id: pr
+        run: echo "::set-output name=id::$(<pr-id.txt)"
+
+      - name: download dist artifact
+        uses: dawidd6/action-download-artifact@v2
+        with:
+          workflow: ${{ github.event.workflow_run.workflow_id }}
+          workflow_conclusion: success
+          name: dist
+
+      - name: upload surge service
+        id: deploy
+        run: |
+          export DEPLOY_DOMAIN=https://ant-design-pro-preview-pr-${{ steps.pr.outputs.id }}.surge.sh
+          npx surge --project ./ --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }}
+
+      - name: update status comment
+        uses: actions-cool/maintain-one-comment@v1.2.1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          body: |
+            🎊 PR Preview has been successfully built and deployed to https://ant-design-pro-preview-pr-${{ steps.pr.outputs.id }}.surge.sh
+
+            <img width="300" src="https://user-images.githubusercontent.com/507615/90250366-88233900-de6e-11ea-95a5-84f0762ffd39.png">
+
+            <!-- Sticky Pull Request Comment -->
+          body-include: '<!-- Sticky Pull Request Comment -->'
+          number: ${{ steps.pr.outputs.id }}
+
+      - name: The job failed
+        if: ${{ failure() }}
+        uses: actions-cool/maintain-one-comment@v1.2.1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          body: |
+            😭 Deploy PR Preview failed.
+
+            <img width="300" src="https://user-images.githubusercontent.com/507615/90250824-4e066700-de6f-11ea-8230-600ecc3d6a6b.png">
+
+            <!-- Sticky Pull Request Comment -->
+          body-include: '<!-- Sticky Pull Request Comment -->'
+          number: ${{ steps.pr.outputs.id }}
+
+  failed:
+    permissions:
+      actions: read # for dawidd6/action-download-artifact to query and download artifacts
+      issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
+      pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
+    runs-on: ubuntu-latest
+    if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure'
+    steps:
+      - name: download pr artifact
+        uses: dawidd6/action-download-artifact@v2
+        with:
+          workflow: ${{ github.event.workflow_run.workflow_id }}
+          name: pr
+
+      - name: save PR id
+        id: pr
+        run: echo "::set-output name=id::$(<pr-id.txt)"
+
+      - name: The job failed
+        uses: actions-cool/maintain-one-comment@v1.2.1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          body: |
+            😭 Deploy PR Preview failed.
+
+            <img width="300" src="https://user-images.githubusercontent.com/507615/90250824-4e066700-de6f-11ea-8230-600ecc3d6a6b.png">
+
+            <!-- Sticky Pull Request Comment -->
+          body-include: '<!-- Sticky Pull Request Comment -->'
+          number: ${{ steps.pr.outputs.id }}
diff --git a/react-ui/.github/workflows/preview-start.yml b/react-ui/.github/workflows/preview-start.yml
new file mode 100644
index 0000000..39f561d
--- /dev/null
+++ b/react-ui/.github/workflows/preview-start.yml
@@ -0,0 +1,24 @@
+name: Preview Start
+
+on: pull_request_target
+
+permissions:
+  contents: read
+
+jobs:
+  preview:
+    permissions:
+      issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
+      pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
+    runs-on: ubuntu-latest
+    steps:
+      - name: create
+        uses: actions-cool/maintain-one-comment@v1.2.1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          body: |
+            ⚡️ Deploying PR Preview...
+            <img src="https://user-images.githubusercontent.com/507615/90240294-8d2abd00-de5b-11ea-8140-4840a0b2d571.gif" width="300" />
+
+            <!-- Sticky Pull Request Comment -->
+          body-include: '<!-- Sticky Pull Request Comment -->'
diff --git a/react-ui/.gitignore b/react-ui/.gitignore
new file mode 100644
index 0000000..82cadd2
--- /dev/null
+++ b/react-ui/.gitignore
@@ -0,0 +1,43 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+**/node_modules
+# roadhog-api-doc ignore
+/src/utils/request-temp.js
+_roadhog-api-doc
+
+# production
+/dist
+
+# misc
+.DS_Store
+npm-debug.log*
+yarn-error.log
+
+/coverage
+.idea
+yarn.lock
+package-lock.json
+*bak
+.vscode
+
+
+# visual studio code
+.history
+*.log
+functions/*
+.temp/**
+
+# umi
+.umi
+.umi-production
+.umi-test
+
+# screenshot
+screenshot
+.firebase
+.eslintcache
+
+build
+
+pnpm-lock.yaml
\ No newline at end of file
diff --git a/react-ui/.husky/commit-msg b/react-ui/.husky/commit-msg
new file mode 100644
index 0000000..b04aa8f
--- /dev/null
+++ b/react-ui/.husky/commit-msg
@@ -0,0 +1,7 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+# Export Git hook params
+export GIT_PARAMS=$*
+
+# npx --no-install fabric verify-commit
diff --git a/react-ui/.husky/pre-commit b/react-ui/.husky/pre-commit
new file mode 100644
index 0000000..6149072
--- /dev/null
+++ b/react-ui/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+# npx --no-install lint-staged
diff --git a/react-ui/.prettierignore b/react-ui/.prettierignore
new file mode 100644
index 0000000..7999ccd
--- /dev/null
+++ b/react-ui/.prettierignore
@@ -0,0 +1,22 @@
+**/*.svg
+.umi
+.umi-production
+/dist
+.dockerignore
+.DS_Store
+.eslintignore
+*.png
+*.toml
+docker
+.editorconfig
+Dockerfile*
+.gitignore
+.prettierignore
+LICENSE
+.eslintcache
+*.lock
+yarn-error.log
+.history
+CNAME
+/build
+/public
diff --git a/react-ui/.prettierrc.js b/react-ui/.prettierrc.js
new file mode 100644
index 0000000..3447a1a
--- /dev/null
+++ b/react-ui/.prettierrc.js
@@ -0,0 +1,21 @@
+module.exports = {
+  singleQuote: true,
+  trailingComma: 'all',
+  printWidth: 100,
+  proseWrap: 'never',
+  endOfLine: 'lf',
+  overrides: [
+    {
+      files: '.prettierrc',
+      options: {
+        parser: 'json',
+      },
+    },
+    {
+      files: 'document.ejs',
+      options: {
+        parser: 'html',
+      },
+    },
+  ],
+};
diff --git a/react-ui/CODE_OF_CONDUCT.md b/react-ui/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..2b4571c
--- /dev/null
+++ b/react-ui/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+- The use of sexualized language or imagery and unwelcome sexual attention or advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at afc163@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/react-ui/LICENSE b/react-ui/LICENSE
new file mode 100644
index 0000000..b65958e
--- /dev/null
+++ b/react-ui/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019-present Alipay.inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/react-ui/README.md b/react-ui/README.md
new file mode 100644
index 0000000..2cf4d67
--- /dev/null
+++ b/react-ui/README.md
@@ -0,0 +1,133 @@
+Language : 🇺🇸 | [🇨🇳](./README.zh-CN.md) | [🇷🇺](./README.ru-RU.md) | [🇹🇷](./README.tr-TR.md) | [🇯🇵](./README.ja-JP.md) | [🇫🇷](./README.fr-FR.md) | [🇵🇹](./README.pt-BR.md) | [🇸🇦](./README.ar-DZ.md)
+
+<h1 align="center">Ant Design Pro</h1>
+
+<div align="center">
+
+An out-of-box UI solution for enterprise applications as a React boilerplate.
+
+[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master) ![Github Action](https://github.com/ant-design/ant-design-pro/workflows/Node%20CI/badge.svg) ![Deploy](https://github.com/ant-design/ant-design-pro/workflows/Deploy%20CI/badge.svg)
+
+[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/) ![](https://badgen.net/badge/icon/Ant%20Design?icon=https://gw.alipayobjects.com/zos/antfincdn/Pp4WPgVDB3/KDpgvguMpGfqaHPjicRK.svg&label)
+
+![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
+
+</div>
+
+- Preview: http://preview.pro.ant.design
+- Home Page: http://pro.ant.design
+- Documentation: http://pro.ant.design/docs/getting-started
+- ChangeLog: http://pro.ant.design/docs/changelog
+- FAQ: http://pro.ant.design/docs/faq
+- Mirror Site in China: http://ant-design-pro.gitee.io
+
+## 5.0 is out! 🎉🎉🎉
+
+[Ant Design Pro 5.0.0](https://github.com/ant-design/ant-design-pro/issues/8656)
+
+## Translation Recruitment :loudspeaker:
+
+We need your help: https://github.com/ant-design/ant-design-pro/issues/120
+
+## Features
+
+- :bulb: **TypeScript**: A language for application-scale JavaScript
+- :scroll: **Blocks**: Build page with block template
+- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
+- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
+- :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd
+- :iphone: **Responsive**: Designed for variable screen sizes
+- :art: **Theming**: Customizable theme with simple config
+- :globe_with_meridians: **International**: Built-in i18n solution
+- :gear: **Best Practices**: Solid workflow to make your code healthy
+- :1234: **Mock development**: Easy to use mock development solution
+- :white_check_mark: **UI Test**: Fly safely with unit and e2e tests
+
+## Templates
+
+```
+- Dashboard
+  - Analytic
+  - Monitor
+  - Workspace
+- Form
+  - Basic Form
+  - Step Form
+  - Advanced From
+- List
+  - Standard Table
+  - Standard List
+  - Card List
+  - Search List (Project/Applications/Article)
+- Profile
+  - Simple Profile
+  - Advanced Profile
+- Account
+  - Account Center
+  - Account Settings
+- Result
+  - Success
+  - Failed
+- Exception
+  - 403
+  - 404
+  - 500
+- User
+  - Login
+  - Register
+  - Register Result
+```
+
+## Usage
+
+### Use bash
+
+We provide pro-cli to quickly initialize scaffolding.
+
+```bash
+# use npm
+npm i @ant-design/pro-cli -g
+pro create myapp
+```
+
+select umi version
+
+```shell
+🐂 Use umi@4 or umi@3 ? (Use arrow keys)
+❯ umi@4
+  umi@3
+```
+
+> If the umi@4 version is selected, full blocks are not yet supported.
+
+If you choose umi@3, you can also choose the pro template. Pro is the basic template, which only provides the basic content of the framework operation. Complete contains all blocks, which is not suitable for secondary development as a basic template.
+
+```shell
+? 🚀 Full or a simple scaffold? (Use arrow keys)
+❯ simple
+  complete
+```
+
+Install dependencies:
+
+```shell
+$ cd myapp && tyarn
+// or
+$ cd myapp && npm install
+```
+
+## Browsers support
+
+Modern browsers.
+
+| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
+| --- | --- | --- | --- | --- |
+| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
+
+## Contributing
+
+Any type of contribution is welcome, here are some examples of how you may contribute to this project:
+
+- Use Ant Design Pro in your daily work.
+- Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions.
+- Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.
diff --git a/react-ui/config/config.ts b/react-ui/config/config.ts
new file mode 100644
index 0000000..f0004a8
--- /dev/null
+++ b/react-ui/config/config.ts
@@ -0,0 +1,156 @@
+// https://umijs.org/config/
+import { defineConfig } from '@umijs/max';
+import { join } from 'path';
+import defaultSettings from './defaultSettings';
+import proxy from './proxy';
+import routes from './routes';
+
+const { REACT_APP_ENV = 'dev' } = process.env;
+
+export default defineConfig({
+  /**
+   * @name 开启 hash 模式
+   * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。
+   * @doc https://umijs.org/docs/api/config#hash
+   */
+  hash: true,
+
+  /**
+   * @name 兼容性设置
+   * @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖
+   * @doc https://umijs.org/docs/api/config#targets
+   */
+  // targets: {
+  //   ie: 11,
+  // },
+  /**
+   * @name 路由的配置,不在路由中引入的文件不会编译
+   * @description 只支持 path,component,routes,redirect,wrappers,title 的配置
+   * @doc https://umijs.org/docs/guides/routes
+   */
+  // umi routes: https://umijs.org/docs/routing
+  routes,
+  /**
+   * @name 主题的配置
+   * @description 虽然叫主题,但是其实只是 less 的变量设置
+   * @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn
+   * @doc umi 的theme 配置 https://umijs.org/docs/api/config#theme
+   */
+  theme: {
+    // 如果不想要 configProvide 动态设置主题需要把这个设置为 default
+    // 只有设置为 variable, 才能使用 configProvide 动态设置主色调
+    'root-entry-name': 'variable',
+  },
+  /**
+   * @name moment 的国际化配置
+   * @description 如果对国际化没有要求,打开之后能减少js的包大小
+   * @doc https://umijs.org/docs/api/config#ignoremomentlocale
+   */
+  ignoreMomentLocale: true,
+  /**
+   * @name 代理配置
+   * @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了
+   * @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。
+   * @doc 代理介绍 https://umijs.org/docs/guides/proxy
+   * @doc 代理配置 https://umijs.org/docs/api/config#proxy
+   */
+  proxy: proxy[REACT_APP_ENV as keyof typeof proxy],
+  /**
+   * @name 快速热更新配置
+   * @description 一个不错的热更新组件,更新时可以保留 state
+   */
+  fastRefresh: true,
+  //============== 以下都是max的插件配置 ===============
+  /**
+   * @name 数据流插件
+   * @@doc https://umijs.org/docs/max/data-flow
+   */
+  model: {},
+  /**
+   * 一个全局的初始数据流,可以用它在插件之间共享数据
+   * @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。
+   * @doc https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81
+   */
+  initialState: {},
+  /**
+   * @name layout 插件
+   * @doc https://umijs.org/docs/max/layout-menu
+   */
+  title: 'Ant Design Pro',
+  layout: {
+    locale: true,
+    ...defaultSettings,
+  },
+  /**
+   * @name moment2dayjs 插件
+   * @description 将项目中的 moment 替换为 dayjs
+   * @doc https://umijs.org/docs/max/moment2dayjs
+   */
+  moment2dayjs: {
+    preset: 'antd',
+    plugins: ['duration'],
+  },
+  /**
+   * @name 国际化插件
+   * @doc https://umijs.org/docs/max/i18n
+   */
+  locale: {
+    // default zh-CN
+    default: 'zh-CN',
+    antd: true,
+    // default true, when it is true, will use `navigator.language` overwrite default
+    baseNavigator: true,
+  },
+  /**
+   * @name antd 插件
+   * @description 内置了 babel import 插件
+   * @doc https://umijs.org/docs/max/antd#antd
+   */
+  antd: {},
+  /**
+   * @name 网络请求配置
+   * @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
+   * @doc https://umijs.org/docs/max/request
+   */
+  request: {},
+  /**
+   * @name 权限插件
+   * @description 基于 initialState 的权限插件,必须先打开 initialState
+   * @doc https://umijs.org/docs/max/access
+   */
+  access: {},
+  /**
+   * @name <head> 中额外的 script
+   * @description 配置 <head> 中额外的 script
+   */
+  headScripts: [
+    // 解决首次加载时白屏的问题
+    { src: '/scripts/loading.js', async: true },
+  ],
+  //================ pro 插件配置 =================
+  presets: ['umi-presets-pro'],
+  /**
+   * @name openAPI 插件的配置
+   * @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码
+   * @doc https://pro.ant.design/zh-cn/docs/openapi/
+   */
+  openAPI: [
+    {
+      requestLibPath: "import { request } from '@umijs/max'",
+      // 或者使用在线的版本
+      // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
+      schemaPath: join(__dirname, 'oneapi.json'),
+      mock: false,
+    },
+    {
+      requestLibPath: "import { request } from '@umijs/max'",
+      schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
+      projectName: 'swagger',
+    },
+  ],
+  mfsu: {
+    strategy: 'normal',
+  },
+  esbuildMinifyIIFE: true,
+  requestRecord: {},
+});
diff --git a/react-ui/config/defaultSettings.ts b/react-ui/config/defaultSettings.ts
new file mode 100644
index 0000000..2796a7e
--- /dev/null
+++ b/react-ui/config/defaultSettings.ts
@@ -0,0 +1,28 @@
+import { ProLayoutProps } from '@ant-design/pro-components';
+
+/**
+ * @name
+ */
+const Settings: ProLayoutProps & {
+  pwa?: boolean;
+  logo?: string;
+} = {
+  navTheme: 'light',
+  // 拂晓蓝
+  colorPrimary: '#1890ff',
+  layout: 'mix',
+  contentWidth: 'Fluid',
+  fixedHeader: false,
+  fixSiderbar: true,
+  colorWeak: false,
+  title: 'Ant Design Pro',
+  pwa: true,
+  logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
+  iconfontUrl: '',
+  token: {
+    // 参见ts声明,demo 见文档,通过token 修改样式
+    //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F
+  },
+};
+
+export default Settings;
diff --git a/react-ui/config/oneapi.json b/react-ui/config/oneapi.json
new file mode 100644
index 0000000..c77d988
--- /dev/null
+++ b/react-ui/config/oneapi.json
@@ -0,0 +1,593 @@
+{
+  "openapi": "3.0.1",
+  "info": {
+    "title": "Ant Design Pro",
+    "version": "1.0.0"
+  },
+  "servers": [
+    {
+      "url": "http://localhost:8000/"
+    },
+    {
+      "url": "https://localhost:8000/"
+    }
+  ],
+  "paths": {
+    "/api/currentUser": {
+      "get": {
+        "tags": ["api"],
+        "description": "获取当前的用户",
+        "operationId": "currentUser",
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/CurrentUser"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        }
+      },
+      "x-swagger-router-controller": "api"
+    },
+    "/api/login/captcha": {
+      "post": {
+        "description": "发送验证码",
+        "operationId": "getFakeCaptcha",
+        "tags": ["login"],
+        "parameters": [
+          {
+            "name": "phone",
+            "in": "query",
+            "description": "手机号",
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/FakeCaptcha"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/api/login/outLogin": {
+      "post": {
+        "description": "登录接口",
+        "operationId": "outLogin",
+        "tags": ["login"],
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        }
+      },
+      "x-swagger-router-controller": "api"
+    },
+    "/api/login/account": {
+      "post": {
+        "tags": ["login"],
+        "description": "登录接口",
+        "operationId": "login",
+        "requestBody": {
+          "description": "登录系统",
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/LoginParams"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/LoginResult"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        },
+        "x-codegen-request-body-name": "body"
+      },
+      "x-swagger-router-controller": "api"
+    },
+    "/api/notices": {
+      "summary": "getNotices",
+      "description": "NoticeIconItem",
+      "get": {
+        "tags": ["api"],
+        "operationId": "getNotices",
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/NoticeIconList"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/api/rule": {
+      "get": {
+        "tags": ["rule"],
+        "description": "获取规则列表",
+        "operationId": "rule",
+        "parameters": [
+          {
+            "name": "current",
+            "in": "query",
+            "description": "当前的页码",
+            "schema": {
+              "type": "number"
+            }
+          },
+          {
+            "name": "pageSize",
+            "in": "query",
+            "description": "页面的容量",
+            "schema": {
+              "type": "number"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/RuleList"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        }
+      },
+      "post": {
+        "tags": ["rule"],
+        "description": "新建规则",
+        "operationId": "addRule",
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/RuleListItem"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        }
+      },
+      "put": {
+        "tags": ["rule"],
+        "description": "新建规则",
+        "operationId": "updateRule",
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/RuleListItem"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        }
+      },
+      "delete": {
+        "tags": ["rule"],
+        "description": "删除规则",
+        "operationId": "removeRule",
+        "responses": {
+          "200": {
+            "description": "Success",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        }
+      },
+      "x-swagger-router-controller": "api"
+    },
+    "/swagger": {
+      "x-swagger-pipe": "swagger_raw"
+    }
+  },
+  "components": {
+    "schemas": {
+      "CurrentUser": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string"
+          },
+          "avatar": {
+            "type": "string"
+          },
+          "userid": {
+            "type": "string"
+          },
+          "email": {
+            "type": "string"
+          },
+          "signature": {
+            "type": "string"
+          },
+          "title": {
+            "type": "string"
+          },
+          "group": {
+            "type": "string"
+          },
+          "tags": {
+            "type": "array",
+            "items": {
+              "type": "object",
+              "properties": {
+                "key": {
+                  "type": "string"
+                },
+                "label": {
+                  "type": "string"
+                }
+              }
+            }
+          },
+          "notifyCount": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "unreadCount": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "country": {
+            "type": "string"
+          },
+          "access": {
+            "type": "string"
+          },
+          "geographic": {
+            "type": "object",
+            "properties": {
+              "province": {
+                "type": "object",
+                "properties": {
+                  "label": {
+                    "type": "string"
+                  },
+                  "key": {
+                    "type": "string"
+                  }
+                }
+              },
+              "city": {
+                "type": "object",
+                "properties": {
+                  "label": {
+                    "type": "string"
+                  },
+                  "key": {
+                    "type": "string"
+                  }
+                }
+              }
+            }
+          },
+          "address": {
+            "type": "string"
+          },
+          "phone": {
+            "type": "string"
+          }
+        }
+      },
+      "LoginResult": {
+        "type": "object",
+        "properties": {
+          "status": {
+            "type": "string"
+          },
+          "type": {
+            "type": "string"
+          },
+          "currentAuthority": {
+            "type": "string"
+          }
+        }
+      },
+      "PageParams": {
+        "type": "object",
+        "properties": {
+          "current": {
+            "type": "number"
+          },
+          "pageSize": {
+            "type": "number"
+          }
+        }
+      },
+      "RuleListItem": {
+        "type": "object",
+        "properties": {
+          "key": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "disabled": {
+            "type": "boolean"
+          },
+          "href": {
+            "type": "string"
+          },
+          "avatar": {
+            "type": "string"
+          },
+          "name": {
+            "type": "string"
+          },
+          "owner": {
+            "type": "string"
+          },
+          "desc": {
+            "type": "string"
+          },
+          "callNo": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "status": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "updatedAt": {
+            "type": "string",
+            "format": "datetime"
+          },
+          "createdAt": {
+            "type": "string",
+            "format": "datetime"
+          },
+          "progress": {
+            "type": "integer",
+            "format": "int32"
+          }
+        }
+      },
+      "RuleList": {
+        "type": "object",
+        "properties": {
+          "data": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/RuleListItem"
+            }
+          },
+          "total": {
+            "type": "integer",
+            "description": "列表的内容总数",
+            "format": "int32"
+          },
+          "success": {
+            "type": "boolean"
+          }
+        }
+      },
+      "FakeCaptcha": {
+        "type": "object",
+        "properties": {
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "status": {
+            "type": "string"
+          }
+        }
+      },
+      "LoginParams": {
+        "type": "object",
+        "properties": {
+          "username": {
+            "type": "string"
+          },
+          "password": {
+            "type": "string"
+          },
+          "autoLogin": {
+            "type": "boolean"
+          },
+          "type": {
+            "type": "string"
+          }
+        }
+      },
+      "ErrorResponse": {
+        "required": ["errorCode"],
+        "type": "object",
+        "properties": {
+          "errorCode": {
+            "type": "string",
+            "description": "业务约定的错误码"
+          },
+          "errorMessage": {
+            "type": "string",
+            "description": "业务上的错误信息"
+          },
+          "success": {
+            "type": "boolean",
+            "description": "业务上的请求是否成功"
+          }
+        }
+      },
+      "NoticeIconList": {
+        "type": "object",
+        "properties": {
+          "data": {
+            "type": "array",
+            "items": {
+              "$ref": "#/components/schemas/NoticeIconItem"
+            }
+          },
+          "total": {
+            "type": "integer",
+            "description": "列表的内容总数",
+            "format": "int32"
+          },
+          "success": {
+            "type": "boolean"
+          }
+        }
+      },
+      "NoticeIconItemType": {
+        "title": "NoticeIconItemType",
+        "description": "已读未读列表的枚举",
+        "type": "string",
+        "properties": {},
+        "enum": ["notification", "message", "event"]
+      },
+      "NoticeIconItem": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "string"
+          },
+          "extra": {
+            "type": "string",
+            "format": "any"
+          },
+          "key": { "type": "string" },
+          "read": {
+            "type": "boolean"
+          },
+          "avatar": {
+            "type": "string"
+          },
+          "title": {
+            "type": "string"
+          },
+          "status": {
+            "type": "string"
+          },
+          "datetime": {
+            "type": "string",
+            "format": "date"
+          },
+          "description": {
+            "type": "string"
+          },
+          "type": {
+            "extensions": {
+              "x-is-enum": true
+            },
+            "$ref": "#/components/schemas/NoticeIconItemType"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/react-ui/config/proxy.ts b/react-ui/config/proxy.ts
new file mode 100644
index 0000000..c290c6a
--- /dev/null
+++ b/react-ui/config/proxy.ts
@@ -0,0 +1,49 @@
+/**
+ * @name 代理的配置
+ * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
+ * -------------------------------
+ * The agent cannot take effect in the production environment
+ * so there is no configuration of the production environment
+ * For details, please see
+ * https://pro.ant.design/docs/deploy
+ *
+ * @doc https://umijs.org/docs/guides/proxy
+ */
+export default {
+  // 如果需要自定义本地开发服务器  请取消注释按需调整
+  dev: {
+    // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
+    '/api/': {
+      // 要代理的地址
+      target: 'http://localhost:8080',
+      // 配置了这个可以从 http 代理到 https
+      // 依赖 origin 的功能可能需要这个,比如 cookie
+      changeOrigin: true,
+      pathRewrite: { '^/api': '' },
+    },
+    '/profile/avatar/': {
+      target: 'http://localhost:8080',
+      changeOrigin: true,
+    }
+  },
+
+  /**
+   * @name 详细的代理配置
+   * @doc https://github.com/chimurai/http-proxy-middleware
+   */
+  test: {
+    // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
+    '/api/': {
+      target: 'https://proapi.azurewebsites.net',
+      changeOrigin: true,
+      pathRewrite: { '^': '' },
+    },
+  },
+  pre: {
+    '/api/': {
+      target: 'your pre url',
+      changeOrigin: true,
+      pathRewrite: { '^': '' },
+    },
+  },
+};
diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts
new file mode 100644
index 0000000..ffad02f
--- /dev/null
+++ b/react-ui/config/routes.ts
@@ -0,0 +1,92 @@
+/**
+ * @name umi 的路由配置
+ * @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置
+ * @param path  path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。
+ * @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。
+ * @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。
+ * @param redirect 配置路由跳转
+ * @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验
+ * @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题
+ * @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 <StepBackwardOutlined /> 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 <UserOutlined /> 则取值应为 user 或者 User
+ * @doc https://umijs.org/docs/guides/routes
+ */
+export default [
+  {
+    path: '/',
+    redirect: '/account/center',
+  },
+  {
+    path: '*',
+    layout: false,
+    component: './404',
+  },
+  {
+    path: '/user',
+    layout: false,
+    routes: [
+      {
+        name: 'login',
+        path: '/user/login',
+        component: './User/Login',
+      },
+    ],
+  },
+  {
+    path: '/account',
+    routes: [
+      {
+        name: 'acenter',
+        path: '/account/center',
+        component: './User/Center',
+      },
+      {
+        name: 'asettings',
+        path: '/account/settings',
+        component: './User/Settings',
+      },
+    ],
+  },
+  {
+    name: 'system',
+    path: '/system',
+    routes: [
+      {
+        name: '字典数据',
+        path: '/system/dict-data/index/:id',
+        component: './System/DictData',
+      },
+      {
+        name: '分配用户',
+        path: '/system/role-auth/user/:id',
+        component: './System/Role/authUser',
+      },
+    ]
+  },
+  {
+    name: 'monitor',
+    path: '/monitor',
+    routes: [
+      {
+        name: '任务日志',
+        path: '/monitor/job-log/index/:id',
+        component: './Monitor/JobLog',
+      },
+    ]
+  },
+  {
+    name: 'tool',
+    path: '/tool',
+    routes: [
+      {
+        name: '导入表',
+        path: '/tool/gen/import',
+        component: './Tool/Gen/import',
+      },
+      {
+        name: '编辑表',
+        path: '/tool/gen/edit',
+        component: './Tool/Gen/edit',
+      },
+    ]
+  },
+];
diff --git a/react-ui/jest.config.ts b/react-ui/jest.config.ts
new file mode 100644
index 0000000..1de2a1a
--- /dev/null
+++ b/react-ui/jest.config.ts
@@ -0,0 +1,23 @@
+import { configUmiAlias, createConfig } from '@umijs/max/test';
+
+export default async () => {
+  const config = await configUmiAlias({
+    ...createConfig({
+      target: 'browser',
+    }),
+  });
+
+  console.log();
+  return {
+    ...config,
+    testEnvironmentOptions: {
+      ...(config?.testEnvironmentOptions || {}),
+      url: 'http://localhost:8000',
+    },
+    setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'],
+    globals: {
+      ...config.globals,
+      localStorage: null,
+    },
+  };
+};
diff --git a/react-ui/jsconfig.json b/react-ui/jsconfig.json
new file mode 100644
index 0000000..197bee5
--- /dev/null
+++ b/react-ui/jsconfig.json
@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "jsx": "react-jsx",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  }
+}
diff --git a/react-ui/mock/listTableList.ts b/react-ui/mock/listTableList.ts
new file mode 100644
index 0000000..35ec3ce
--- /dev/null
+++ b/react-ui/mock/listTableList.ts
@@ -0,0 +1,176 @@
+import { Request, Response } from 'express';
+import moment from 'moment';
+import { parse } from 'url';
+
+// mock tableListDataSource
+const genList = (current: number, pageSize: number) => {
+  const tableListDataSource: API.RuleListItem[] = [];
+
+  for (let i = 0; i < pageSize; i += 1) {
+    const index = (current - 1) * 10 + i;
+    tableListDataSource.push({
+      key: index,
+      disabled: i % 6 === 0,
+      href: 'https://ant.design',
+      avatar: [
+        'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+      ][i % 2],
+      name: `TradeCode ${index}`,
+      owner: '曲丽丽',
+      desc: '这是一段描述',
+      callNo: Math.floor(Math.random() * 1000),
+      status: Math.floor(Math.random() * 10) % 4,
+      updatedAt: moment().format('YYYY-MM-DD'),
+      createdAt: moment().format('YYYY-MM-DD'),
+      progress: Math.ceil(Math.random() * 100),
+    });
+  }
+  tableListDataSource.reverse();
+  return tableListDataSource;
+};
+
+let tableListDataSource = genList(1, 100);
+
+function getRule(req: Request, res: Response, u: string) {
+  let realUrl = u;
+  if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
+    realUrl = req.url;
+  }
+  const { current = 1, pageSize = 10 } = req.query;
+  const params = parse(realUrl, true).query as unknown as API.PageParams &
+    API.RuleListItem & {
+      sorter: any;
+      filter: any;
+    };
+
+  let dataSource = [...tableListDataSource].slice(
+    ((current as number) - 1) * (pageSize as number),
+    (current as number) * (pageSize as number),
+  );
+  if (params.sorter) {
+    const sorter = JSON.parse(params.sorter);
+    dataSource = dataSource.sort((prev, next) => {
+      let sortNumber = 0;
+      (Object.keys(sorter) as Array<keyof API.RuleListItem>).forEach((key) => {
+        let nextSort = next?.[key] as number;
+        let preSort = prev?.[key] as number;
+        if (sorter[key] === 'descend') {
+          if (preSort - nextSort > 0) {
+            sortNumber += -1;
+          } else {
+            sortNumber += 1;
+          }
+          return;
+        }
+        if (preSort - nextSort > 0) {
+          sortNumber += 1;
+        } else {
+          sortNumber += -1;
+        }
+      });
+      return sortNumber;
+    });
+  }
+  if (params.filter) {
+    const filter = JSON.parse(params.filter as any) as {
+      [key: string]: string[];
+    };
+    if (Object.keys(filter).length > 0) {
+      dataSource = dataSource.filter((item) => {
+        return (Object.keys(filter) as Array<keyof API.RuleListItem>).some((key) => {
+          if (!filter[key]) {
+            return true;
+          }
+          if (filter[key].includes(`${item[key]}`)) {
+            return true;
+          }
+          return false;
+        });
+      });
+    }
+  }
+
+  if (params.name) {
+    dataSource = dataSource.filter((data) => data?.name?.includes(params.name || ''));
+  }
+  const result = {
+    data: dataSource,
+    total: tableListDataSource.length,
+    success: true,
+    pageSize,
+    current: parseInt(`${params.current}`, 10) || 1,
+  };
+
+  return res.json(result);
+}
+
+function postRule(req: Request, res: Response, u: string, b: Request) {
+  let realUrl = u;
+  if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
+    realUrl = req.url;
+  }
+
+  const body = (b && b.body) || req.body;
+  const { method, name, desc, key } = body;
+
+  switch (method) {
+    /* eslint no-case-declarations:0 */
+    case 'delete':
+      tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1);
+      break;
+    case 'post':
+      (() => {
+        const i = Math.ceil(Math.random() * 10000);
+        const newRule: API.RuleListItem = {
+          key: tableListDataSource.length,
+          href: 'https://ant.design',
+          avatar: [
+            'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+            'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+          ][i % 2],
+          name,
+          owner: '曲丽丽',
+          desc,
+          callNo: Math.floor(Math.random() * 1000),
+          status: Math.floor(Math.random() * 10) % 2,
+          updatedAt: moment().format('YYYY-MM-DD'),
+          createdAt: moment().format('YYYY-MM-DD'),
+          progress: Math.ceil(Math.random() * 100),
+        };
+        tableListDataSource.unshift(newRule);
+        return res.json(newRule);
+      })();
+      return;
+
+    case 'update':
+      (() => {
+        let newRule = {};
+        tableListDataSource = tableListDataSource.map((item) => {
+          if (item.key === key) {
+            newRule = { ...item, desc, name };
+            return { ...item, desc, name };
+          }
+          return item;
+        });
+        return res.json(newRule);
+      })();
+      return;
+    default:
+      break;
+  }
+
+  const result = {
+    list: tableListDataSource,
+    pagination: {
+      total: tableListDataSource.length,
+    },
+  };
+
+  res.json(result);
+}
+
+export default {
+  'GET /api/rule': getRule,
+  'POST /api/rule': postRule,
+};
diff --git a/react-ui/mock/notices.ts b/react-ui/mock/notices.ts
new file mode 100644
index 0000000..616c921
--- /dev/null
+++ b/react-ui/mock/notices.ts
@@ -0,0 +1,115 @@
+import { Request, Response } from 'express';
+
+const getNotices = (req: Request, res: Response) => {
+  res.json({
+    data: [
+      {
+        id: '000000001',
+        avatar:
+          'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/MSbDR4FR2MUAAAAAAAAAAAAAFl94AQBr',
+        title: '你收到了 14 份新周报',
+        datetime: '2017-08-09',
+        type: 'notification',
+      },
+      {
+        id: '000000002',
+        avatar:
+          'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/hX-PTavYIq4AAAAAAAAAAAAAFl94AQBr',
+        title: '你推荐的 曲妮妮 已通过第三轮面试',
+        datetime: '2017-08-08',
+        type: 'notification',
+      },
+      {
+        id: '000000003',
+        avatar:
+          'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/jHX5R5l3QjQAAAAAAAAAAAAAFl94AQBr',
+        title: '这种模板可以区分多种通知类型',
+        datetime: '2017-08-07',
+        read: true,
+        type: 'notification',
+      },
+      {
+        id: '000000004',
+        avatar:
+          'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Wr4mQqx6jfwAAAAAAAAAAAAAFl94AQBr',
+        title: '左侧图标用于区分不同的类型',
+        datetime: '2017-08-07',
+        type: 'notification',
+      },
+      {
+        id: '000000005',
+        avatar:
+          'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Mzj_TbcWUj4AAAAAAAAAAAAAFl94AQBr',
+        title: '内容不要超过两行字,超出时自动截断',
+        datetime: '2017-08-07',
+        type: 'notification',
+      },
+      {
+        id: '000000006',
+        avatar:
+          'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/eXLzRbPqQE4AAAAAAAAAAAAAFl94AQBr',
+        title: '曲丽丽 评论了你',
+        description: '描述信息描述信息描述信息',
+        datetime: '2017-08-07',
+        type: 'message',
+        clickClose: true,
+      },
+      {
+        id: '000000007',
+        avatar:
+          'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/w5mRQY2AmEEAAAAAAAAAAAAAFl94AQBr',
+        title: '朱偏右 回复了你',
+        description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+        datetime: '2017-08-07',
+        type: 'message',
+        clickClose: true,
+      },
+      {
+        id: '000000008',
+        avatar:
+          'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/wPadR5M9918AAAAAAAAAAAAAFl94AQBr',
+        title: '标题',
+        description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+        datetime: '2017-08-07',
+        type: 'message',
+        clickClose: true,
+      },
+      {
+        id: '000000009',
+        title: '任务名称',
+        description: '任务需要在 2017-01-12 20:00 前启动',
+        extra: '未开始',
+        status: 'todo',
+        type: 'event',
+      },
+      {
+        id: '000000010',
+        title: '第三方紧急代码变更',
+        description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+        extra: '马上到期',
+        status: 'urgent',
+        type: 'event',
+      },
+      {
+        id: '000000011',
+        title: '信息安全考试',
+        description: '指派竹尔于 2017-01-09 前完成更新并发布',
+        extra: '已耗时 8 天',
+        status: 'doing',
+        type: 'event',
+      },
+      {
+        id: '000000012',
+        title: 'ABCD 版本发布',
+        description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+        extra: '进行中',
+        status: 'processing',
+        type: 'event',
+      },
+    ],
+  });
+};
+
+export default {
+  'GET /api/notices': getNotices,
+};
diff --git a/react-ui/mock/requestRecord.mock.js b/react-ui/mock/requestRecord.mock.js
new file mode 100644
index 0000000..6c59e19
--- /dev/null
+++ b/react-ui/mock/requestRecord.mock.js
@@ -0,0 +1,324 @@
+module.exports = {
+  'GET /api/currentUser': {
+    data: {
+      name: 'Serati Ma',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
+      userid: '00000001',
+      email: 'antdesign@alipay.com',
+      signature: '海纳百川,有容乃大',
+      title: '交互专家',
+      group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
+      tags: [
+        { key: '0', label: '很有想法的' },
+        { key: '1', label: '专注设计' },
+        { key: '2', label: '辣~' },
+        { key: '3', label: '大长腿' },
+        { key: '4', label: '川妹子' },
+        { key: '5', label: '海纳百川' },
+      ],
+      notifyCount: 12,
+      unreadCount: 11,
+      country: 'China',
+      geographic: {
+        province: { label: '浙江省', key: '330000' },
+        city: { label: '杭州市', key: '330100' },
+      },
+      address: '西湖区工专路 77 号',
+      phone: '0752-268888888',
+    },
+  },
+  'GET /api/rule': {
+    data: [
+      {
+        key: 99,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 99',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 503,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 81,
+      },
+      {
+        key: 98,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 98',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 164,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 12,
+      },
+      {
+        key: 97,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 97',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 174,
+        status: '1',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 81,
+      },
+      {
+        key: 96,
+        disabled: true,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 96',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 914,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 7,
+      },
+      {
+        key: 95,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 95',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 698,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 82,
+      },
+      {
+        key: 94,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 94',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 488,
+        status: '1',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 14,
+      },
+      {
+        key: 93,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 93',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 580,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 77,
+      },
+      {
+        key: 92,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 92',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 244,
+        status: '3',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 58,
+      },
+      {
+        key: 91,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 91',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 959,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 66,
+      },
+      {
+        key: 90,
+        disabled: true,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 90',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 958,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 72,
+      },
+      {
+        key: 89,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 89',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 301,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 2,
+      },
+      {
+        key: 88,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 88',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 277,
+        status: '1',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 12,
+      },
+      {
+        key: 87,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 87',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 810,
+        status: '1',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 82,
+      },
+      {
+        key: 86,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 86',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 780,
+        status: '3',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 22,
+      },
+      {
+        key: 85,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 85',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 705,
+        status: '3',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 12,
+      },
+      {
+        key: 84,
+        disabled: true,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 84',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 203,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 79,
+      },
+      {
+        key: 83,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 83',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 491,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 59,
+      },
+      {
+        key: 82,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 82',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 73,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 100,
+      },
+      {
+        key: 81,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 81',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 406,
+        status: '3',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 61,
+      },
+      {
+        key: 80,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 80',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 112,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 20,
+      },
+    ],
+    total: 100,
+    success: true,
+    pageSize: 20,
+    current: 1,
+  },
+  'POST /api/login/outLogin': { data: {}, success: true },
+  'POST /api/login/account': {
+    status: 'ok',
+    type: 'account',
+    currentAuthority: 'admin',
+  },
+};
diff --git a/react-ui/mock/route.ts b/react-ui/mock/route.ts
new file mode 100644
index 0000000..418d10f
--- /dev/null
+++ b/react-ui/mock/route.ts
@@ -0,0 +1,5 @@
+export default {
+  '/api/auth_routes': {
+    '/form/advanced-form': { authority: ['admin', 'user'] },
+  },
+};
diff --git a/react-ui/mock/user.ts b/react-ui/mock/user.ts
new file mode 100644
index 0000000..75edd34
--- /dev/null
+++ b/react-ui/mock/user.ts
@@ -0,0 +1,203 @@
+import { Request, Response } from 'express';
+
+const waitTime = (time: number = 100) => {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(true);
+    }, time);
+  });
+};
+
+async function getFakeCaptcha(req: Request, res: Response) {
+  await waitTime(2000);
+  return res.json('captcha-xxx');
+}
+
+const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env;
+
+/**
+ * 当前用户的权限,如果为空代表没登录
+ * current user access, if is '', user need login
+ * 如果是 pro 的预览,默认是有权限的
+ */
+let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : '';
+
+const getAccess = () => {
+  return access;
+};
+
+// 代码中会兼容本地 service mock 以及部署站点的静态数据
+export default {
+  // 支持值为 Object 和 Array
+  'GET /api/currentUser': (req: Request, res: Response) => {
+    if (!getAccess()) {
+      res.status(401).send({
+        data: {
+          isLogin: false,
+        },
+        errorCode: '401',
+        errorMessage: '请先登录!',
+        success: true,
+      });
+      return;
+    }
+    res.send({
+      success: true,
+      data: {
+        name: 'Serati Ma',
+        avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
+        userid: '00000001',
+        email: 'antdesign@alipay.com',
+        signature: '海纳百川,有容乃大',
+        title: '交互专家',
+        group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
+        tags: [
+          {
+            key: '0',
+            label: '很有想法的',
+          },
+          {
+            key: '1',
+            label: '专注设计',
+          },
+          {
+            key: '2',
+            label: '辣~',
+          },
+          {
+            key: '3',
+            label: '大长腿',
+          },
+          {
+            key: '4',
+            label: '川妹子',
+          },
+          {
+            key: '5',
+            label: '海纳百川',
+          },
+        ],
+        notifyCount: 12,
+        unreadCount: 11,
+        country: 'China',
+        access: getAccess(),
+        geographic: {
+          province: {
+            label: '浙江省',
+            key: '330000',
+          },
+          city: {
+            label: '杭州市',
+            key: '330100',
+          },
+        },
+        address: '西湖区工专路 77 号',
+        phone: '0752-268888888',
+      },
+    });
+  },
+  // GET POST 可省略
+  'GET /api/users': [
+    {
+      key: '1',
+      name: 'John Brown',
+      age: 32,
+      address: 'New York No. 1 Lake Park',
+    },
+    {
+      key: '2',
+      name: 'Jim Green',
+      age: 42,
+      address: 'London No. 1 Lake Park',
+    },
+    {
+      key: '3',
+      name: 'Joe Black',
+      age: 32,
+      address: 'Sidney No. 1 Lake Park',
+    },
+  ],
+  'POST /api/login/account': async (req: Request, res: Response) => {
+    const { password, username, type } = req.body;
+    await waitTime(2000);
+    if (password === 'ant.design' && username === 'admin') {
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'admin',
+      });
+      access = 'admin';
+      return;
+    }
+    if (password === 'ant.design' && username === 'user') {
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'user',
+      });
+      access = 'user';
+      return;
+    }
+    if (type === 'mobile') {
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'admin',
+      });
+      access = 'admin';
+      return;
+    }
+
+    res.send({
+      status: 'error',
+      type,
+      currentAuthority: 'guest',
+    });
+    access = 'guest';
+  },
+  'POST /api/login/outLogin': (req: Request, res: Response) => {
+    access = '';
+    res.send({ data: {}, success: true });
+  },
+  'POST /api/register': (req: Request, res: Response) => {
+    res.send({ status: 'ok', currentAuthority: 'user', success: true });
+  },
+  'GET /api/500': (req: Request, res: Response) => {
+    res.status(500).send({
+      timestamp: 1513932555104,
+      status: 500,
+      error: 'error',
+      message: 'error',
+      path: '/base/category/list',
+    });
+  },
+  'GET /api/404': (req: Request, res: Response) => {
+    res.status(404).send({
+      timestamp: 1513932643431,
+      status: 404,
+      error: 'Not Found',
+      message: 'No message available',
+      path: '/base/category/list/2121212',
+    });
+  },
+  'GET /api/403': (req: Request, res: Response) => {
+    res.status(403).send({
+      timestamp: 1513932555104,
+      status: 403,
+      error: 'Forbidden',
+      message: 'Forbidden',
+      path: '/base/category/list',
+    });
+  },
+  'GET /api/401': (req: Request, res: Response) => {
+    res.status(401).send({
+      timestamp: 1513932555104,
+      status: 401,
+      error: 'Unauthorized',
+      message: 'Unauthorized',
+      path: '/base/category/list',
+    });
+  },
+
+  'GET  /api/login/captcha': getFakeCaptcha,
+};
diff --git a/react-ui/package.json b/react-ui/package.json
new file mode 100644
index 0000000..4763d98
--- /dev/null
+++ b/react-ui/package.json
@@ -0,0 +1,143 @@
+{
+  "name": "ant-design-pro",
+  "version": "6.0.0",
+  "private": true,
+  "description": "An out-of-box UI solution for enterprise applications",
+  "scripts": {
+    "dev": "npm run start:dev",
+    "build": "max build",
+    "deploy": "npm run build && npm run gh-pages",
+    "preview": "npm run build && max preview --port 8000",
+    "serve": "umi-serve",
+    "start": "cross-env UMI_ENV=dev max dev",
+    "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev",
+    "start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev",
+    "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
+    "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
+    "test": "jest",
+    "test:coverage": "npm run jest -- --coverage",
+    "test:update": "npm run jest -- -u",
+    "docker-hub:build": "docker build  -f Dockerfile.hub -t  ant-design-pro ./",
+    "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
+    "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
+    "docker:build": "docker-compose -f ./docker/docker-compose.dev.yml build",
+    "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
+    "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro",
+    "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
+    "analyze": "cross-env ANALYZE=1 max build",
+    "gh-pages": "gh-pages -d dist",
+    "i18n-remove": "pro i18n-remove --locale=zh-CN --write",
+    "postinstall": "max setup",
+    "jest": "jest",
+    "lint": "npm run lint:js && npm run lint:prettier && npm run tsc",
+    "lint-staged": "lint-staged",
+    "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
+    "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src ",
+    "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
+    "lint:prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto",
+    "openapi": "max openapi",
+    "prepare": "cd .. && husky install",
+    "prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"",
+    "tsc": "tsc --noEmit",
+    "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login"
+  },
+  "lint-staged": {
+    "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
+    "**/*.{js,jsx,tsx,ts,less,md,json}": [
+      "prettier --write"
+    ]
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 10"
+  ],
+  "dependencies": {
+    "@ant-design/icons": "^5.5.0",
+    "@ant-design/plots": "^2.3.2",
+    "@ant-design/pro-components": "^2.7.19",
+    "@ant-design/use-emotion-css": "1.0.4",
+    "@testing-library/dom": "^10.4.0",
+    "@umijs/route-utils": "^4.0.1",
+    "ant-design-pro": "file:",
+    "antd": "^5.21.1",
+    "antd-style": "^3.6.2",
+    "classnames": "^2.5.1",
+    "dayjs": "^1.11.13",
+    "fabric": "^6.4.0",
+    "highlight.js": "^11.10.0",
+    "lodash": "^4.17.21",
+    "moment": "^2.30.1",
+    "omit.js": "^2.0.2",
+    "query-string": "^9.1.0",
+    "rc-menu": "^9.15.0",
+    "rc-util": "^5.43.0",
+    "react": "^18.3.0",
+    "react-cropper": "^2.3.3",
+    "react-dev-inspector": "^2.0.1",
+    "react-dom": "^18.3.0",
+    "react-helmet-async": "^2.0.0",
+    "react-highlight": "^0.15.0"
+  },
+  "devDependencies": {
+    "@ant-design/pro-cli": "^3.3.0",
+    "@testing-library/react": "^16.0.1",
+    "@types/classnames": "^2.3.1",
+    "@types/express": "^4.17.21",
+    "@types/history": "^4.7.11",
+    "@types/jest": "^29.5.12",
+    "@types/lodash": "^4.17.4",
+    "@types/react": "^18.3.0",
+    "@types/react-dom": "^18.3.0",
+    "@types/react-helmet": "^6.1.11",
+    "@umijs/fabric": "^2.14.1",
+    "@umijs/lint": "^4.2.9",
+    "@umijs/max": "^4.2.9",
+    "cross-env": "^7.0.3",
+    "eslint": "^9.11.0",
+    "express": "^4.21.0",
+    "gh-pages": "^6.1.0",
+    "husky": "^9.1.3",
+    "jest": "^29.7.0",
+    "jest-environment-jsdom": "^29.7.0",
+    "lint-staged": "^15.2.0",
+    "mockjs": "^1.1.0",
+    "prettier": "^3.3.0",
+    "swagger-ui-dist": "^5.17.14",
+    "ts-node": "^10.9.1",
+    "typescript": "^5.6.2",
+    "umi-presets-pro": "^2.0.0"
+  },
+  "engines": {
+    "node": ">=12.0.0"
+  },
+  "create-umi": {
+    "ignoreScript": [
+      "docker*",
+      "functions*",
+      "site",
+      "generateMock"
+    ],
+    "ignoreDependencies": [
+      "netlify*",
+      "serverless"
+    ],
+    "ignore": [
+      ".dockerignore",
+      ".git",
+      ".github",
+      ".gitpod.yml",
+      "CODE_OF_CONDUCT.md",
+      "Dockerfile",
+      "Dockerfile.*",
+      "lambda",
+      "LICENSE",
+      "netlify.toml",
+      "README.*.md",
+      "azure-pipelines.yml",
+      "docker",
+      "CNAME",
+      "create-umi"
+    ]
+  }
+}
diff --git a/react-ui/public/CNAME b/react-ui/public/CNAME
new file mode 100644
index 0000000..30c2d4d
--- /dev/null
+++ b/react-ui/public/CNAME
@@ -0,0 +1 @@
+preview.pro.ant.design
\ No newline at end of file
diff --git a/react-ui/public/favicon.ico b/react-ui/public/favicon.ico
new file mode 100644
index 0000000..e2e9325
--- /dev/null
+++ b/react-ui/public/favicon.ico
Binary files differ
diff --git a/react-ui/public/icons/icon-128x128.png b/react-ui/public/icons/icon-128x128.png
new file mode 100644
index 0000000..48d0e23
--- /dev/null
+++ b/react-ui/public/icons/icon-128x128.png
Binary files differ
diff --git a/react-ui/public/icons/icon-192x192.png b/react-ui/public/icons/icon-192x192.png
new file mode 100644
index 0000000..938e9b5
--- /dev/null
+++ b/react-ui/public/icons/icon-192x192.png
Binary files differ
diff --git a/react-ui/public/icons/icon-512x512.png b/react-ui/public/icons/icon-512x512.png
new file mode 100644
index 0000000..21fc108
--- /dev/null
+++ b/react-ui/public/icons/icon-512x512.png
Binary files differ
diff --git a/react-ui/public/logo.svg b/react-ui/public/logo.svg
new file mode 100644
index 0000000..239bf69
--- /dev/null
+++ b/react-ui/public/logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg>
\ No newline at end of file
diff --git a/react-ui/public/pro_icon.svg b/react-ui/public/pro_icon.svg
new file mode 100644
index 0000000..e075b78
--- /dev/null
+++ b/react-ui/public/pro_icon.svg
@@ -0,0 +1,5 @@
+<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg">
+ <g>
+  <path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/react-ui/public/scripts/loading.js b/react-ui/public/scripts/loading.js
new file mode 100644
index 0000000..c1ced54
--- /dev/null
+++ b/react-ui/public/scripts/loading.js
@@ -0,0 +1,202 @@
+/**
+ * loading 占位
+ * 解决首次加载时白屏的问题
+ */
+ (function () {
+  const _root = document.querySelector('#root');
+  if (_root && _root.innerHTML === '') {
+    _root.innerHTML = `
+      <style>
+        html,
+        body,
+        #root {
+          height: 100%;
+          margin: 0;
+          padding: 0;
+        }
+        #root {
+          background-repeat: no-repeat;
+          background-size: 100% auto;
+        }
+
+        .loading-title {
+          font-size: 1.1rem;
+        }
+
+        .loading-sub-title {
+          margin-top: 20px;
+          font-size: 1rem;
+          color: #888;
+        }
+
+        .page-loading-warp {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          padding: 26px;
+        }
+        .ant-spin {
+          position: absolute;
+          display: none;
+          -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+          margin: 0;
+          padding: 0;
+          color: rgba(0, 0, 0, 0.65);
+          color: #1890ff;
+          font-size: 14px;
+          font-variant: tabular-nums;
+          line-height: 1.5;
+          text-align: center;
+          list-style: none;
+          opacity: 0;
+          -webkit-transition: -webkit-transform 0.3s
+            cubic-bezier(0.78, 0.14, 0.15, 0.86);
+          transition: -webkit-transform 0.3s
+            cubic-bezier(0.78, 0.14, 0.15, 0.86);
+          transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+          transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
+            -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+          -webkit-font-feature-settings: "tnum";
+          font-feature-settings: "tnum";
+        }
+
+        .ant-spin-spinning {
+          position: static;
+          display: inline-block;
+          opacity: 1;
+        }
+
+        .ant-spin-dot {
+          position: relative;
+          display: inline-block;
+          width: 20px;
+          height: 20px;
+          font-size: 20px;
+        }
+
+        .ant-spin-dot-item {
+          position: absolute;
+          display: block;
+          width: 9px;
+          height: 9px;
+          background-color: #1890ff;
+          border-radius: 100%;
+          -webkit-transform: scale(0.75);
+          -ms-transform: scale(0.75);
+          transform: scale(0.75);
+          -webkit-transform-origin: 50% 50%;
+          -ms-transform-origin: 50% 50%;
+          transform-origin: 50% 50%;
+          opacity: 0.3;
+          -webkit-animation: antspinmove 1s infinite linear alternate;
+          animation: antSpinMove 1s infinite linear alternate;
+        }
+
+        .ant-spin-dot-item:nth-child(1) {
+          top: 0;
+          left: 0;
+        }
+
+        .ant-spin-dot-item:nth-child(2) {
+          top: 0;
+          right: 0;
+          -webkit-animation-delay: 0.4s;
+          animation-delay: 0.4s;
+        }
+
+        .ant-spin-dot-item:nth-child(3) {
+          right: 0;
+          bottom: 0;
+          -webkit-animation-delay: 0.8s;
+          animation-delay: 0.8s;
+        }
+
+        .ant-spin-dot-item:nth-child(4) {
+          bottom: 0;
+          left: 0;
+          -webkit-animation-delay: 1.2s;
+          animation-delay: 1.2s;
+        }
+
+        .ant-spin-dot-spin {
+          -webkit-transform: rotate(45deg);
+          -ms-transform: rotate(45deg);
+          transform: rotate(45deg);
+          -webkit-animation: antrotate 1.2s infinite linear;
+          animation: antRotate 1.2s infinite linear;
+        }
+
+        .ant-spin-lg .ant-spin-dot {
+          width: 32px;
+          height: 32px;
+          font-size: 32px;
+        }
+
+        .ant-spin-lg .ant-spin-dot i {
+          width: 14px;
+          height: 14px;
+        }
+
+        @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+          .ant-spin-blur {
+            background: #fff;
+            opacity: 0.5;
+          }
+        }
+
+        @-webkit-keyframes antSpinMove {
+          to {
+            opacity: 1;
+          }
+        }
+
+        @keyframes antSpinMove {
+          to {
+            opacity: 1;
+          }
+        }
+
+        @-webkit-keyframes antRotate {
+          to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+          }
+        }
+
+        @keyframes antRotate {
+          to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+          }
+        }
+      </style>
+
+      <div style="
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        height: 100%;
+        min-height: 362px;
+      ">
+        <div class="page-loading-warp">
+          <div class="ant-spin ant-spin-lg ant-spin-spinning">
+            <span class="ant-spin-dot ant-spin-dot-spin">
+              <i class="ant-spin-dot-item"></i>
+              <i class="ant-spin-dot-item"></i>
+              <i class="ant-spin-dot-item"></i>
+              <i class="ant-spin-dot-item"></i>
+            </span>
+          </div>
+        </div>
+        <div class="loading-title">
+          正在加载资源
+        </div>
+        <div class="loading-sub-title">
+          初次加载资源可能需要较多时间 请耐心等待
+        </div>
+      </div>
+    `;
+  }
+})();
diff --git a/react-ui/src/access.ts b/react-ui/src/access.ts
new file mode 100644
index 0000000..8c11f31
--- /dev/null
+++ b/react-ui/src/access.ts
@@ -0,0 +1,55 @@
+import { checkRole, matchPermission } from './utils/permission';
+/**
+ * @see https://umijs.org/zh-CN/plugins/plugin-access
+ * */
+export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) {
+  const { currentUser } = initialState ?? {};
+  const hasPerms = (perm: string) => {
+    return matchPermission(initialState?.currentUser?.permissions, perm);
+  };
+  const roleFiler = (route: { authority: string[] }) => {
+    return checkRole(initialState?.currentUser?.roles, route.authority);
+  };
+  return {
+    canAdmin: currentUser && currentUser.access === 'admin',
+    hasPerms,
+    roleFiler,
+  };
+}
+
+export function setSessionToken(
+  access_token: string | undefined,
+  refresh_token: string | undefined,
+  expireTime: number,
+): void {
+  if (access_token) {
+    localStorage.setItem('access_token', access_token);
+  } else {
+    localStorage.removeItem('access_token');
+  }
+  if (refresh_token) {
+    localStorage.setItem('refresh_token', refresh_token);
+  } else {
+    localStorage.removeItem('refresh_token');
+  }
+  localStorage.setItem('expireTime', `${expireTime}`);
+}
+
+export function getAccessToken() {
+  return localStorage.getItem('access_token');
+}
+
+export function getRefreshToken() {
+  return localStorage.getItem('refresh_token');
+}
+
+export function getTokenExpireTime() {
+  return localStorage.getItem('expireTime');
+}
+
+export function clearSessionToken() {
+  sessionStorage.removeItem('user');
+  localStorage.removeItem('access_token');
+  localStorage.removeItem('refresh_token');
+  localStorage.removeItem('expireTime');
+}
diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx
new file mode 100644
index 0000000..38f76b4
--- /dev/null
+++ b/react-ui/src/app.tsx
@@ -0,0 +1,233 @@
+import { Footer, Question, SelectLang, AvatarDropdown, AvatarName } from '@/components';
+import { LinkOutlined } from '@ant-design/icons';
+import type { Settings as LayoutSettings } from '@ant-design/pro-components';
+import { SettingDrawer } from '@ant-design/pro-components';
+import type { RunTimeLayoutConfig } from '@umijs/max';
+import { history, Link } from '@umijs/max';
+import defaultSettings from '../config/defaultSettings';
+import { errorConfig } from './requestErrorConfig';
+import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access';
+import { getRemoteMenu, getRoutersInfo, getUserInfo, patchRouteWithRemoteMenus, setRemoteMenu } from './services/session';
+import { PageEnum } from './enums/pagesEnums';
+
+
+const isDev = process.env.NODE_ENV === 'development';
+
+
+
+/**
+ * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
+ * */
+export async function getInitialState(): Promise<{
+  settings?: Partial<LayoutSettings>;
+  currentUser?: API.CurrentUser;
+  loading?: boolean;
+  fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
+}> {
+  const fetchUserInfo = async () => {
+    try {
+      const response = await getUserInfo({
+        skipErrorHandler: true,
+      });
+      if (response.user.avatar === '') {
+        response.user.avatar =
+          'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png';
+      }
+      return {
+        ...response.user,
+        permissions: response.permissions,
+        roles: response.roles,
+      } as API.CurrentUser;
+    } catch (error) {
+      console.log(error);
+      history.push(PageEnum.LOGIN);
+    }
+    return undefined;
+  };
+  // 如果不是登录页面,执行
+  const { location } = history;
+  if (location.pathname !== PageEnum.LOGIN) {
+    const currentUser = await fetchUserInfo();
+    return {
+      fetchUserInfo,
+      currentUser,
+      settings: defaultSettings as Partial<LayoutSettings>,
+    };
+  }
+  return {
+    fetchUserInfo,
+    settings: defaultSettings as Partial<LayoutSettings>,
+  };
+}
+
+// ProLayout 支持的api https://procomponents.ant.design/components/layout
+export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
+  return {
+    actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />],
+    avatarProps: {
+      src: initialState?.currentUser?.avatar,
+      title: <AvatarName />,
+      render: (_, avatarChildren) => {
+        return <AvatarDropdown menu="True">{avatarChildren}</AvatarDropdown>;
+      },
+    },
+    waterMarkProps: {
+      // content: initialState?.currentUser?.nickName,
+    },
+    menu: {
+      locale: false,
+      // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
+      params: {
+        userId: initialState?.currentUser?.userId,
+      },
+      request: async () => {
+        if (!initialState?.currentUser?.userId) {
+          return [];
+        }
+        return getRemoteMenu();
+      },
+    },
+    footerRender: () => <Footer />,
+    onPageChange: () => {
+      const { location } = history;
+      // 如果没有登录,重定向到 login
+      if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) {
+        history.push(PageEnum.LOGIN);
+      }
+    },
+    layoutBgImgList: [
+      {
+        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
+        left: 85,
+        bottom: 100,
+        height: '303px',
+      },
+      {
+        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
+        bottom: -68,
+        right: -45,
+        height: '303px',
+      },
+      {
+        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
+        bottom: 0,
+        left: 0,
+        width: '331px',
+      },
+    ],
+    links: isDev
+      ? [
+        <Link key="openapi" to="/umi/plugin/openapi" target="_blank">
+          <LinkOutlined />
+          <span>OpenAPI 文档</span>
+        </Link>,
+      ]
+      : [],
+    menuHeaderRender: undefined,
+    // 自定义 403 页面
+    // unAccessible: <div>unAccessible</div>,
+    // 增加一个 loading 的状态
+    childrenRender: (children) => {
+      // if (initialState?.loading) return <PageLoading />;
+      return (
+        <>
+          {children}
+          <SettingDrawer
+            disableUrlParams
+            enableDarkTheme
+            settings={initialState?.settings}
+            onSettingChange={(settings) => {
+              setInitialState((preInitialState) => ({
+                ...preInitialState,
+                settings,
+              }));
+            }}
+          />
+        </>
+      );
+    },
+    ...initialState?.settings,
+  };
+};
+
+export async function onRouteChange({ clientRoutes, location }) {
+  const menus = getRemoteMenu();
+ // console.log('onRouteChange', clientRoutes, location, menus);
+  if(menus === null && location.pathname !== PageEnum.LOGIN) {
+    console.log('refresh')
+    history.go(0);
+  }
+}
+
+// export function patchRoutes({ routes, routeComponents }) {
+//   console.log('patchRoutes', routes, routeComponents);
+// }
+
+
+export async function patchClientRoutes({ routes }) {
+  // console.log('patchClientRoutes', routes);
+  patchRouteWithRemoteMenus(routes);
+}
+
+export function render(oldRender: () => void) {
+  // console.log('render get routers', oldRender)
+  const token = getAccessToken();
+  if(!token || token?.length === 0) {
+    oldRender();
+    return;
+  }
+  getRoutersInfo().then(res => {
+    setRemoteMenu(res);
+    oldRender()
+  });
+}
+
+/**
+ * @name request 配置,可以配置错误处理
+ * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
+ * @doc https://umijs.org/docs/max/request#配置
+ */
+const checkRegion = 5 * 60 * 1000;
+
+export const request = {
+  ...errorConfig,
+  requestInterceptors: [
+    (url: any, options: { headers: any }) => {
+      const headers = options.headers ? options.headers : [];
+      console.log('request ====>:', url);
+      const authHeader = headers['Authorization'];
+      const isToken = headers['isToken'];
+      if (!authHeader && isToken !== false) {
+        const expireTime = getTokenExpireTime();
+        if (expireTime) {
+          const left = Number(expireTime) - new Date().getTime();
+          const refreshToken = getRefreshToken();
+          if (left < checkRegion && refreshToken) {
+            if (left < 0) {
+              clearSessionToken();
+            }
+          } else {
+            const accessToken = getAccessToken();
+            if (accessToken) {
+              headers['Authorization'] = `Bearer ${accessToken}`;
+            }
+          }
+        } else {
+          clearSessionToken();
+        }
+      }
+      return { url, options };
+    },
+  ],
+  responseInterceptors: [
+    // (response) =>
+    // {
+    //   // // 不再需要异步处理读取返回体内容,可直接在data中读出,部分字段可在 config 中找到
+    //   // const { data = {} as any, config } = response;
+    //   // // do something
+    //   // console.log('data: ', data)
+    //   // console.log('config: ', config)
+    //   return response
+    // },
+  ],
+};
diff --git a/react-ui/src/components/DictTag/index.tsx b/react-ui/src/components/DictTag/index.tsx
new file mode 100644
index 0000000..2b2a791
--- /dev/null
+++ b/react-ui/src/components/DictTag/index.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { Tag } from 'antd';
+import { ProSchemaValueEnumType } from '@ant-design/pro-components';
+import { DefaultOptionType } from 'antd/es/select';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/10
+ *
+ * */
+
+export interface DictValueEnumType extends ProSchemaValueEnumType {
+    id?: string | number;
+    key?: string | number;
+    value: string | number;
+    label: string;
+    listClass?: string;
+}
+
+export interface DictOptionType extends DefaultOptionType {
+    id?: string | number;
+    key?: string | number;
+    text: string;
+    listClass?: string;
+}
+
+
+export type DictValueEnumObj = Record<string | number, DictValueEnumType>;
+
+export type DictTagProps = {
+    key?: string;
+    value?: string | number;
+    enums?: DictValueEnumObj;
+    options?: DictOptionType[];
+};
+
+const DictTag: React.FC<DictTagProps> = (props) => {
+    function getDictColor(type?: string) {
+        switch (type) {
+            case 'primary':
+                return 'blue';
+            case 'success':
+                return 'success';
+            case 'info':
+                return 'green';
+            case 'warning':
+                return 'warning';
+            case 'danger':
+                return 'error';
+            case 'default':
+            default:
+                return 'default';
+        }
+    }
+
+    function getDictLabelByValue(value: string | number | undefined): string {
+        if (value === undefined) {
+            return '';
+        }
+        if (props.enums) {
+            const item = props.enums[value];
+            return item.label;
+        }
+        if (props.options) {
+            if (!Array.isArray(props.options)) {
+                console.log('DictTag options is no array!')
+                return '';
+            }
+            for (const item of props.options) {
+                if (item.value === value) {
+                    return item.text;
+                }
+            }
+        }
+        return String(props.value);
+    }
+
+    function getDictListClassByValue(value: string | number | undefined): string {
+        if (value === undefined) {
+            return 'default';
+        }
+        if (props.enums) {
+            const item = props.enums[value];
+            return item.listClass || 'default';
+        }
+        if (props.options) {
+            if (!Array.isArray(props.options)) {
+                console.log('DictTag options is no array!')
+                return 'default';
+            }
+            for (const item of props.options) {
+                if (item.value === value) {
+                    return item.listClass || 'default';
+                }
+            }
+        }
+        return String(props.value);
+    }
+
+    const getTagColor = () => {
+        return getDictColor(getDictListClassByValue(props.value).toLowerCase());
+    };
+
+    const getTagText = (): string => {
+        return getDictLabelByValue(props.value);
+    };
+
+    return (
+        <Tag color={getTagColor()}>{getTagText()}</Tag>
+    )
+}
+
+
+export default DictTag;
\ No newline at end of file
diff --git a/react-ui/src/components/Footer/index.tsx b/react-ui/src/components/Footer/index.tsx
new file mode 100644
index 0000000..f204ac2
--- /dev/null
+++ b/react-ui/src/components/Footer/index.tsx
@@ -0,0 +1,35 @@
+import { GithubOutlined } from '@ant-design/icons';
+import { DefaultFooter } from '@ant-design/pro-components';
+import React from 'react';
+
+const Footer: React.FC = () => {
+  return (
+    <DefaultFooter
+      style={{
+        background: 'none',
+      }}
+      links={[
+        {
+          key: 'Ant Design Pro',
+          title: 'Ant Design Pro',
+          href: 'https://pro.ant.design',
+          blankTarget: true,
+        },
+        {
+          key: 'github',
+          title: <GithubOutlined />,
+          href: 'https://github.com/ant-design/ant-design-pro',
+          blankTarget: true,
+        },
+        {
+          key: 'Ant Design',
+          title: 'Ant Design',
+          href: 'https://ant.design',
+          blankTarget: true,
+        },
+      ]}
+    />
+  );
+};
+
+export default Footer;
diff --git a/react-ui/src/components/HeaderDropdown/index.tsx b/react-ui/src/components/HeaderDropdown/index.tsx
new file mode 100644
index 0000000..f89052d
--- /dev/null
+++ b/react-ui/src/components/HeaderDropdown/index.tsx
@@ -0,0 +1,27 @@
+import { Dropdown } from 'antd';
+import type { DropDownProps } from 'antd/es/dropdown';
+import React from 'react';
+import { createStyles } from 'antd-style';
+import classNames from 'classnames';
+
+const useStyles = createStyles(({ token }) => {
+  return {
+    dropdown: {
+      [`@media screen and (max-width: ${token.screenXS}px)`]: {
+        width: '100%',
+      },
+    },
+  };
+});
+
+export type HeaderDropdownProps = {
+  overlayClassName?: string;
+  placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
+} & Omit<DropDownProps, 'overlay'>;
+
+const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => {
+  const { styles } = useStyles();
+  return <Dropdown overlayClassName={classNames(styles.dropdown, cls)} {...restProps} />;
+};
+
+export default HeaderDropdown;
diff --git a/react-ui/src/components/IconSelector/Category.tsx b/react-ui/src/components/IconSelector/Category.tsx
new file mode 100644
index 0000000..dd0e93f
--- /dev/null
+++ b/react-ui/src/components/IconSelector/Category.tsx
@@ -0,0 +1,63 @@
+import * as React from 'react';
+import CopyableIcon from './CopyableIcon';
+import type { ThemeType } from './index';
+import type { CategoriesKeys } from './fields';
+import { useIntl } from '@umijs/max';
+import styles from './style.less';
+
+interface CategoryProps {
+  title: CategoriesKeys;
+  icons: string[];
+  theme: ThemeType;
+  newIcons: string[];
+  onSelect: (type: string, name: string) => any;
+}
+
+const Category: React.FC<CategoryProps> = props => {
+
+  const { icons, title, newIcons, theme } = props;
+  const intl = useIntl();
+  const [justCopied, setJustCopied] = React.useState<string | null>(null);
+  const copyId = React.useRef<NodeJS.Timeout | null>(null);
+  const onSelect = React.useCallback((type: string, text: string) => {
+    const { onSelect } = props;
+    if (onSelect) {
+      onSelect(type, text);
+    }
+    setJustCopied(type);
+    copyId.current = setTimeout(() => {
+      setJustCopied(null);
+    }, 2000);
+  }, []);
+  React.useEffect(
+    () => () => {
+      if (copyId.current) {
+        clearTimeout(copyId.current);
+      }
+    },
+    [],
+  );
+
+  return (
+    <div>
+      <h4>{intl.formatMessage({
+        id: `app.docs.components.icon.category.${title}`,
+        defaultMessage: '信息',
+      })}</h4>
+      <ul className={styles.anticonsList}>
+        {icons.map(name => (
+          <CopyableIcon
+            key={name}
+            name={name}
+            theme={theme}
+            isNew={newIcons.includes(name)}
+            justCopied={justCopied}
+            onSelect={onSelect}
+          />
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export default Category;
diff --git a/react-ui/src/components/IconSelector/CopyableIcon.tsx b/react-ui/src/components/IconSelector/CopyableIcon.tsx
new file mode 100644
index 0000000..371cba0
--- /dev/null
+++ b/react-ui/src/components/IconSelector/CopyableIcon.tsx
@@ -0,0 +1,47 @@
+import * as React from 'react';
+import { Tooltip } from 'antd';
+import classNames from 'classnames';
+import * as AntdIcons from '@ant-design/icons';
+import type { ThemeType } from './index';
+import styles from './style.less';
+
+const allIcons: {
+  [key: string]: any;
+} = AntdIcons;
+
+export interface CopyableIconProps {
+  name: string;
+  isNew: boolean;
+  theme: ThemeType;
+  justCopied: string | null;
+  onSelect: (type: string, text: string) => any;
+}
+
+const CopyableIcon: React.FC<CopyableIconProps> = ({
+  name,
+  justCopied,
+  onSelect,
+  theme,
+}) => {
+  const className = classNames({
+    copied: justCopied === name,
+    [theme]: !!theme,
+  });
+  return (
+    <li className={className}
+      onClick={() => {
+        if (onSelect) {
+          onSelect(theme, name);
+        }
+      }}>
+      <Tooltip title={name}>
+        {React.createElement(allIcons[name], { className: styles.anticon })}
+      </Tooltip>
+      {/* <span className={styles.anticonClass}>
+          <Badge dot={isNew}>{name}</Badge>
+        </span> */}
+    </li>
+  );
+};
+
+export default CopyableIcon;
diff --git a/react-ui/src/components/IconSelector/IconPicSearcher.tsx b/react-ui/src/components/IconSelector/IconPicSearcher.tsx
new file mode 100644
index 0000000..3a4cf01
--- /dev/null
+++ b/react-ui/src/components/IconSelector/IconPicSearcher.tsx
@@ -0,0 +1,233 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { Upload, Tooltip, Popover, Modal, Progress, Spin, Result } from 'antd';
+import * as AntdIcons from '@ant-design/icons';
+import { useIntl } from '@umijs/max';
+import './style.less';
+
+const allIcons: { [key: string]: any } = AntdIcons;
+
+const { Dragger } = Upload;
+interface AntdIconClassifier {
+  load: () => void;
+  predict: (imgEl: HTMLImageElement) => void;
+}
+declare global {
+  interface Window {
+    antdIconClassifier: AntdIconClassifier;
+  }
+}
+
+interface PicSearcherState {
+  loading: boolean;
+  modalOpen: boolean;
+  popoverVisible: boolean;
+  icons: iconObject[];
+  fileList: any[];
+  error: boolean;
+  modelLoaded: boolean;
+}
+
+interface iconObject {
+  type: string;
+  score: number;
+}
+
+const PicSearcher: React.FC = () => {
+  const intl = useIntl();
+  const {formatMessage} = intl;
+  const [state, setState] = useState<PicSearcherState>({
+    loading: false,
+    modalOpen: false,
+    popoverVisible: false,
+    icons: [],
+    fileList: [],
+    error: false,
+    modelLoaded: false,
+  });
+  const predict = (imgEl: HTMLImageElement) => {
+    try {
+      let icons: any[] = window.antdIconClassifier.predict(imgEl);
+      if (gtag && icons.length) {
+        gtag('event', 'icon', {
+          event_category: 'search-by-image',
+          event_label: icons[0].className,
+        });
+      }
+      icons = icons.map(i => ({ score: i.score, type: i.className.replace(/\s/g, '-') }));
+      setState(prev => ({ ...prev, loading: false, error: false, icons }));
+    } catch {
+      setState(prev => ({ ...prev, loading: false, error: true }));
+    }
+  };
+  // eslint-disable-next-line class-methods-use-this
+  const toImage = (url: string) =>
+    new Promise(resolve => {
+      const img = new Image();
+      img.setAttribute('crossOrigin', 'anonymous');
+      img.src = url;
+      img.onload = () => {
+        resolve(img);
+      };
+    });
+
+  const uploadFile = useCallback((file: File) => {
+    setState(prev => ({ ...prev, loading: true }));
+    const reader = new FileReader();
+    reader.onload = () => {
+      toImage(reader.result as string).then(predict);
+      setState(prev => ({
+        ...prev,
+        fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }],
+      }));
+    };
+    reader.readAsDataURL(file);
+  }, []);
+
+  const onPaste = useCallback((event: ClipboardEvent) => {
+    const items = event.clipboardData && event.clipboardData.items;
+    let file = null;
+    if (items && items.length) {
+      for (let i = 0; i < items.length; i++) {
+        if (items[i].type.includes('image')) {
+          file = items[i].getAsFile();
+          break;
+        }
+      }
+    }
+    if (file) {
+      uploadFile(file);
+    }
+  }, []);
+  const toggleModal = useCallback(() => {
+    setState(prev => ({
+      ...prev,
+      modalOpen: !prev.modalOpen,
+      popoverVisible: false,
+      fileList: [],
+      icons: [],
+    }));
+    if (!localStorage.getItem('disableIconTip')) {
+      localStorage.setItem('disableIconTip', 'true');
+    }
+  }, []);
+
+  useEffect(() => {
+    const script = document.createElement('script');
+    script.onload = async () => {
+      await window.antdIconClassifier.load();
+      setState(prev => ({ ...prev, modelLoaded: true }));
+      document.addEventListener('paste', onPaste);
+    };
+    script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js';
+    document.head.appendChild(script);
+    setState(prev => ({ ...prev, popoverVisible: !localStorage.getItem('disableIconTip') }));
+    return () => {
+      document.removeEventListener('paste', onPaste);
+    };
+  }, []);
+
+  return (
+    <div className="iconPicSearcher">
+      <Popover
+        content={formatMessage({id: 'app.docs.components.icon.pic-searcher.intro'})}
+        open={state.popoverVisible}
+      >
+        <AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
+      </Popover>
+      <Modal
+        title={intl.formatMessage({
+          id: 'app.docs.components.icon.pic-searcher.title',
+          defaultMessage: '信息',
+        })}
+        open={state.modalOpen}
+        onCancel={toggleModal}
+        footer={null}
+      >
+        {state.modelLoaded || (
+          <Spin
+            spinning={!state.modelLoaded}
+            tip={formatMessage({
+              id: 'app.docs.components.icon.pic-searcher.modelloading',
+
+            })}
+          >
+            <div style={{ height: 100 }} />
+          </Spin>
+        )}
+        {state.modelLoaded && (
+          <Dragger
+            accept="image/jpeg, image/png"
+            listType="picture"
+            customRequest={o => uploadFile(o.file as File)}
+            fileList={state.fileList}
+            showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
+          >
+            <p className="ant-upload-drag-icon">
+              <AntdIcons.InboxOutlined />
+            </p>
+            <p className="ant-upload-text">
+              {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-text'})}
+            </p>
+            <p className="ant-upload-hint">
+              {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-hint'})}
+            </p>
+          </Dragger>
+        )}
+        <Spin
+          spinning={state.loading}
+          tip={formatMessage({id: 'app.docs.components.icon.pic-searcher.matching'})}
+        >
+          <div className="icon-pic-search-result">
+            {state.icons.length > 0 && (
+              <div className="result-tip">
+                {formatMessage({id: 'app.docs.components.icon.pic-searcher.result-tip'})}
+              </div>
+            )}
+            <table>
+              {state.icons.length > 0 && (
+                <thead>
+                  <tr>
+                    <th className="col-icon">
+                      {formatMessage({id: 'app.docs.components.icon.pic-searcher.th-icon'})}
+                    </th>
+                    <th>{formatMessage({id: 'app.docs.components.icon.pic-searcher.th-score'})}</th>
+                  </tr>
+                </thead>
+              )}
+              <tbody>
+                {state.icons.map(icon => {
+                  const { type } = icon;
+                  const iconName = `${type
+                    .split('-')
+                    .map(str => `${str[0].toUpperCase()}${str.slice(1)}`)
+                    .join('')}Outlined`;
+                  return (
+                    <tr key={iconName}>
+                      <td className="col-icon">
+                          <Tooltip title={icon.type} placement="right">
+                            {React.createElement(allIcons[iconName])}
+                          </Tooltip>
+                      </td>
+                      <td>
+                        <Progress percent={Math.ceil(icon.score * 100)} />
+                      </td>
+                    </tr>
+                  );
+                })}
+              </tbody>
+            </table>
+            {state.error && (
+              <Result
+                status="500"
+                title="503"
+                subTitle={formatMessage({id: 'app.docs.components.icon.pic-searcher.server-error'})}
+              />
+            )}
+          </div>
+        </Spin>
+      </Modal>
+    </div>
+  );
+};
+
+export default PicSearcher;
diff --git a/react-ui/src/components/IconSelector/fields.ts b/react-ui/src/components/IconSelector/fields.ts
new file mode 100644
index 0000000..de37e67
--- /dev/null
+++ b/react-ui/src/components/IconSelector/fields.ts
@@ -0,0 +1,223 @@
+import * as AntdIcons from '@ant-design/icons/lib/icons';
+
+const all = Object.keys(AntdIcons)
+  .map(n => n.replace(/(Outlined|Filled|TwoTone)$/, ''))
+  .filter((n, i, arr) => arr.indexOf(n) === i);
+
+const direction = [
+  'StepBackward',
+  'StepForward',
+  'FastBackward',
+  'FastForward',
+  'Shrink',
+  'ArrowsAlt',
+  'Down',
+  'Up',
+  'Left',
+  'Right',
+  'CaretUp',
+  'CaretDown',
+  'CaretLeft',
+  'CaretRight',
+  'UpCircle',
+  'DownCircle',
+  'LeftCircle',
+  'RightCircle',
+  'DoubleRight',
+  'DoubleLeft',
+  'VerticalLeft',
+  'VerticalRight',
+  'VerticalAlignTop',
+  'VerticalAlignMiddle',
+  'VerticalAlignBottom',
+  'Forward',
+  'Backward',
+  'Rollback',
+  'Enter',
+  'Retweet',
+  'Swap',
+  'SwapLeft',
+  'SwapRight',
+  'ArrowUp',
+  'ArrowDown',
+  'ArrowLeft',
+  'ArrowRight',
+  'PlayCircle',
+  'UpSquare',
+  'DownSquare',
+  'LeftSquare',
+  'RightSquare',
+  'Login',
+  'Logout',
+  'MenuFold',
+  'MenuUnfold',
+  'BorderBottom',
+  'BorderHorizontal',
+  'BorderInner',
+  'BorderOuter',
+  'BorderLeft',
+  'BorderRight',
+  'BorderTop',
+  'BorderVerticle',
+  'PicCenter',
+  'PicLeft',
+  'PicRight',
+  'RadiusBottomleft',
+  'RadiusBottomright',
+  'RadiusUpleft',
+  'RadiusUpright',
+  'Fullscreen',
+  'FullscreenExit',
+];
+
+const suggestion = [
+  'Question',
+  'QuestionCircle',
+  'Plus',
+  'PlusCircle',
+  'Pause',
+  'PauseCircle',
+  'Minus',
+  'MinusCircle',
+  'PlusSquare',
+  'MinusSquare',
+  'Info',
+  'InfoCircle',
+  'Exclamation',
+  'ExclamationCircle',
+  'Close',
+  'CloseCircle',
+  'CloseSquare',
+  'Check',
+  'CheckCircle',
+  'CheckSquare',
+  'ClockCircle',
+  'Warning',
+  'IssuesClose',
+  'Stop',
+];
+
+const editor = [
+  'Edit',
+  'Form',
+  'Copy',
+  'Scissor',
+  'Delete',
+  'Snippets',
+  'Diff',
+  'Highlight',
+  'AlignCenter',
+  'AlignLeft',
+  'AlignRight',
+  'BgColors',
+  'Bold',
+  'Italic',
+  'Underline',
+  'Strikethrough',
+  'Redo',
+  'Undo',
+  'ZoomIn',
+  'ZoomOut',
+  'FontColors',
+  'FontSize',
+  'LineHeight',
+  'Dash',
+  'SmallDash',
+  'SortAscending',
+  'SortDescending',
+  'Drag',
+  'OrderedList',
+  'UnorderedList',
+  'RadiusSetting',
+  'ColumnWidth',
+  'ColumnHeight',
+];
+
+const data = [
+  'AreaChart',
+  'PieChart',
+  'BarChart',
+  'DotChart',
+  'LineChart',
+  'RadarChart',
+  'HeatMap',
+  'Fall',
+  'Rise',
+  'Stock',
+  'BoxPlot',
+  'Fund',
+  'Sliders',
+];
+
+const logo = [
+  'Android',
+  'Apple',
+  'Windows',
+  'Ie',
+  'Chrome',
+  'Github',
+  'Aliwangwang',
+  'Dingding',
+  'WeiboSquare',
+  'WeiboCircle',
+  'TaobaoCircle',
+  'Html5',
+  'Weibo',
+  'Twitter',
+  'Wechat',
+  'Youtube',
+  'AlipayCircle',
+  'Taobao',
+  'Skype',
+  'Qq',
+  'MediumWorkmark',
+  'Gitlab',
+  'Medium',
+  'Linkedin',
+  'GooglePlus',
+  'Dropbox',
+  'Facebook',
+  'Codepen',
+  'CodeSandbox',
+  'CodeSandboxCircle',
+  'Amazon',
+  'Google',
+  'CodepenCircle',
+  'Alipay',
+  'AntDesign',
+  'AntCloud',
+  'Aliyun',
+  'Zhihu',
+  'Slack',
+  'SlackSquare',
+  'Behance',
+  'BehanceSquare',
+  'Dribbble',
+  'DribbbleSquare',
+  'Instagram',
+  'Yuque',
+  'Alibaba',
+  'Yahoo',
+  'Reddit',
+  'Sketch',
+  'WhatsApp',
+  'Dingtalk',
+];
+
+const datum = [...direction, ...suggestion, ...editor, ...data, ...logo];
+
+const other = all.filter(n => !datum.includes(n));
+
+export const categories = {
+  direction,
+  suggestion,
+  editor,
+  data,
+  logo,
+  other,
+};
+
+export default categories;
+
+export type Categories = typeof categories;
+export type CategoriesKeys = keyof Categories;
diff --git a/react-ui/src/components/IconSelector/index.tsx b/react-ui/src/components/IconSelector/index.tsx
new file mode 100644
index 0000000..78dc931
--- /dev/null
+++ b/react-ui/src/components/IconSelector/index.tsx
@@ -0,0 +1,142 @@
+import * as React from 'react';
+import Icon, * as AntdIcons from '@ant-design/icons';
+import { Radio, Input, Empty } from 'antd';
+import type { RadioChangeEvent } from 'antd/es/radio/interface';
+import debounce from 'lodash/debounce';
+import Category from './Category';
+import IconPicSearcher from './IconPicSearcher';
+import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
+import type { CategoriesKeys } from './fields';
+import { categories } from './fields';
+// import { useIntl } from '@umijs/max';
+
+export enum ThemeType {
+  Filled = 'Filled',
+  Outlined = 'Outlined',
+  TwoTone = 'TwoTone',
+}
+
+const allIcons: { [key: string]: any } = AntdIcons;
+
+interface IconSelectorProps {
+  //intl: any;
+  onSelect: any;
+}
+
+interface IconSelectorState {
+  theme: ThemeType;
+  searchKey: string;
+}
+
+const IconSelector: React.FC<IconSelectorProps> = (props) => {
+  // const intl = useIntl();
+  // const { messages } = intl;
+  const { onSelect } = props;
+  const [displayState, setDisplayState] = React.useState<IconSelectorState>({
+    theme: ThemeType.Outlined,
+    searchKey: '',
+  });
+
+  const newIconNames: string[] = [];
+
+  const handleSearchIcon = React.useCallback(
+    debounce((searchKey: string) => {
+      setDisplayState(prevState => ({ ...prevState, searchKey }));
+    }),
+    [],
+  );
+
+  const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => {
+    setDisplayState(prevState => ({ ...prevState, theme: e.target.value as ThemeType }));
+  }, []);
+
+  const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
+    const { searchKey = '', theme } = displayState;
+
+    const categoriesResult = Object.keys(categories)
+      .map((key: CategoriesKeys) => {
+        let iconList = categories[key];
+        if (searchKey) {
+          const matchKey = searchKey
+            // eslint-disable-next-line prefer-regex-literals
+            .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name)
+            .replace(/(Filled|Outlined|TwoTone)$/, '')
+            .toLowerCase();
+          iconList = iconList.filter((iconName:string) => iconName.toLowerCase().includes(matchKey));
+        }
+
+        // CopyrightCircle is same as Copyright, don't show it
+        iconList = iconList.filter((icon:string) => icon !== 'CopyrightCircle');
+
+        return {
+          category: key,
+          icons: iconList.map((iconName:string) => iconName + theme).filter((iconName:string) => allIcons[iconName]),
+        };
+      })
+      .filter(({ icons }) => !!icons.length)
+      .map(({ category, icons }) => (
+        <Category
+          key={category}
+          title={category as CategoriesKeys}
+          theme={theme}
+          icons={icons}
+          newIcons={newIconNames}
+          onSelect={(type, name) => {
+            if (onSelect) {
+              onSelect(name, allIcons[name]);
+            }
+          }}
+        />
+      ));
+    return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
+  }, [displayState.searchKey, displayState.theme]);
+  return (
+    <>
+      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+        <Radio.Group
+          value={displayState.theme}
+          onChange={handleChangeTheme}
+          size="large"
+          optionType="button"
+          buttonStyle="solid"
+          options={[
+            {
+              label:  <Icon component={OutlinedIcon} />,
+              value: ThemeType.Outlined
+            },
+            {
+              label: <Icon component={FilledIcon} />,
+              value: ThemeType.Filled
+            },
+            {
+              label: <Icon component={TwoToneIcon} />,
+              value: ThemeType.TwoTone
+            },
+          ]}
+        >
+          {/* <Radio.Button value={ThemeType.Outlined}>
+            <Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
+          </Radio.Button>
+          <Radio.Button value={ThemeType.Filled}>
+            <Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
+          </Radio.Button>
+          <Radio.Button value={ThemeType.TwoTone}>
+            <Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
+          </Radio.Button> */}
+        </Radio.Group>
+        <Input.Search
+          // placeholder={messages['app.docs.components.icon.search.placeholder']}
+          style={{ margin: '0 10px', flex: 1 }}
+          allowClear
+          onChange={e => handleSearchIcon(e.currentTarget.value)}
+          size="large"
+          autoFocus
+          suffix={<IconPicSearcher />}
+        />
+      </div>
+      {renderCategories}
+    </>
+  );
+};
+
+export default IconSelector
diff --git a/react-ui/src/components/IconSelector/style.less b/react-ui/src/components/IconSelector/style.less
new file mode 100644
index 0000000..0a4353d
--- /dev/null
+++ b/react-ui/src/components/IconSelector/style.less
@@ -0,0 +1,137 @@
+.iconPicSearcher {
+  display: inline-block;
+  margin: 0 8px;
+
+  .icon-pic-btn {
+    color: @text-color-secondary;
+    cursor: pointer;
+    transition: all 0.3s;
+
+    &:hover {
+      color: @input-icon-hover-color;
+    }
+  }
+}
+
+.icon-pic-preview {
+  width: 30px;
+  height: 30px;
+  margin-top: 10px;
+  padding: 8px;
+  text-align: center;
+  border: 1px solid @border-color-base;
+  border-radius: 4px;
+
+  > img {
+    max-width: 50px;
+    max-height: 50px;
+  }
+}
+
+.icon-pic-search-result {
+  min-height: 50px;
+  padding: 0 10px;
+
+  > .result-tip {
+    padding: 10px 0;
+    color: @text-color-secondary;
+  }
+
+  > table {
+    width: 100%;
+
+    .col-icon {
+      width: 80px;
+      padding: 10px 0;
+
+      > .anticon {
+        font-size: 30px;
+
+        :hover {
+          color: @link-hover-color;
+        }
+      }
+    }
+  }
+}
+
+ul.anticonsList {
+  margin: 2px 0;
+  overflow: hidden;
+  direction: ltr;
+  list-style: none;
+
+  li {
+    position: relative;
+    float: left;
+    width: 48px;
+    height: 48px;
+    margin: 3px 0;
+    padding: 2px 0 0;
+    overflow: hidden;
+    color: #555;
+    text-align: center;
+    list-style: none;
+    background-color: inherit;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
+
+    .rtl & {
+      margin: 3px 0;
+      padding: 2px 0 0;
+    }
+
+    .anticon {
+      margin: 4px 0 2px;
+      font-size: 24px;
+      transition: transform 0.3s ease-in-out;
+      will-change: transform;
+    }
+
+    .anticonClass {
+      display: block;
+      font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+      white-space: nowrap;
+      text-align: center;
+      transform: scale(0.83);
+
+      .ant-badge {
+        transition: color 0.3s ease-in-out;
+      }
+    }
+
+    &:hover {
+      color: #fff;
+      background-color: @primary-color;
+
+      .anticon {
+        transform: scale(1.4);
+      }
+
+      .ant-badge {
+        color: #fff;
+      }
+    }
+
+    &.TwoTone:hover {
+      background-color: #8ecafe;
+    }
+
+    &.copied:hover {
+      color: rgba(255, 255, 255, 0.2);
+    }
+
+    &.copied::after {
+      top: -2px;
+      opacity: 1;
+    }
+  }
+}
+
+.copied-code {
+  padding: 2px 4px;
+  font-size: 12px;
+  background: #f5f5f5;
+  border-radius: 2px;
+}
diff --git a/react-ui/src/components/IconSelector/themeIcons.tsx b/react-ui/src/components/IconSelector/themeIcons.tsx
new file mode 100644
index 0000000..abefe04
--- /dev/null
+++ b/react-ui/src/components/IconSelector/themeIcons.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+
+
+export const FilledIcon: React.FC = props => {
+  const path =
+    'M864 64H160C107 64 64 107 64 160v' +
+    '704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
+    '0c0-53-43-96-96-96z';
+  return (
+    <svg {...props} viewBox="0 0 1024 1024">
+      <path d={path} />
+    </svg>
+  );
+};
+
+export const OutlinedIcon: React.FC = props => {
+  const path =
+    'M864 64H160C107 64 64 107 64 160v7' +
+    '04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
+    '0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' +
+    '12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' +
+    ' 12 12v680c0 6.6-5.4 12-12 12z';
+  return (
+    <svg {...props} viewBox="0 0 1024 1024">
+      <path d={path} />
+    </svg>
+  );
+};
+
+export const TwoToneIcon: React.FC = props => {
+  const path =
+    'M16 512c0 273.932 222.066 496 496 49' +
+    '6s496-222.068 496-496S785.932 16 512 16 16 238.' +
+    '066 16 512z m496 368V144c203.41 0 368 164.622 3' +
+    '68 368 0 203.41-164.622 368-368 368z';
+  return (
+    <svg {...props} viewBox="0 0 1024 1024">
+      <path d={path} />
+    </svg>
+  );
+};
diff --git a/react-ui/src/components/RightContent/AvatarDropdown.tsx b/react-ui/src/components/RightContent/AvatarDropdown.tsx
new file mode 100644
index 0000000..34ceb15
--- /dev/null
+++ b/react-ui/src/components/RightContent/AvatarDropdown.tsx
@@ -0,0 +1,142 @@
+import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
+import { history, useModel } from '@umijs/max';
+import { Spin } from 'antd';
+import { createStyles } from 'antd-style';
+import { stringify } from 'querystring';
+import type { MenuInfo } from 'rc-menu/lib/interface';
+import React, { useCallback } from 'react';
+import { flushSync } from 'react-dom';
+import HeaderDropdown from '../HeaderDropdown';
+import { setRemoteMenu } from '@/services/session';
+import { clearSessionToken } from '@/access';
+import { logout } from '@/services/system/auth';
+
+export type GlobalHeaderRightProps = {
+  menu?: boolean;
+  children?: React.ReactNode;
+};
+
+export const AvatarName = () => {
+  const { initialState } = useModel('@@initialState');
+  const { currentUser } = initialState || {};
+  return <span className="anticon">{currentUser?.nickName}</span>;
+};
+
+const useStyles = createStyles(({ token }) => {
+  return {
+    action: {
+      display: 'flex',
+      height: '48px',
+      marginLeft: 'auto',
+      overflow: 'hidden',
+      alignItems: 'center',
+      padding: '0 8px',
+      cursor: 'pointer',
+      borderRadius: token.borderRadius,
+      '&:hover': {
+        backgroundColor: token.colorBgTextHover,
+      },
+    },
+  };
+});
+
+export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, children }) => {
+  /**
+   * 退出登录,并且将当前的 url 保存
+   */
+  const loginOut = async () => {
+    await logout();
+    clearSessionToken();
+    setRemoteMenu(null);
+    const { search, pathname } = window.location;
+    const urlParams = new URL(window.location.href).searchParams;
+    /** 此方法会跳转到 redirect 参数所在的位置 */
+    const redirect = urlParams.get('redirect');
+    // Note: There may be security issues, please note
+    if (window.location.pathname !== '/user/login' && !redirect) {
+      history.replace({
+        pathname: '/user/login',
+        search: stringify({
+          redirect: pathname + search,
+        }),
+      });
+    }
+  };
+  const { styles } = useStyles();
+
+  const { initialState, setInitialState } = useModel('@@initialState');
+
+  const onMenuClick = useCallback(
+    (event: MenuInfo) => {
+      const { key } = event;
+      if (key === 'logout') {
+        flushSync(() => {
+          setInitialState((s) => ({ ...s, currentUser: undefined }));
+        });
+        loginOut();
+        return;
+      }
+      history.push(`/account/${key}`);
+    },
+    [setInitialState],
+  );
+
+  const loading = (
+    <span className={styles.action}>
+      <Spin
+        size="small"
+        style={{
+          marginLeft: 8,
+          marginRight: 8,
+        }}
+      />
+    </span>
+  );
+
+  if (!initialState) {
+    return loading;
+  }
+
+  const { currentUser } = initialState;
+
+  if (!currentUser || !currentUser.nickName) {
+    return loading;
+  }
+
+  const menuItems = [
+    ...(menu
+      ? [
+          {
+            key: 'center',
+            icon: <UserOutlined />,
+            label: '个人中心',
+          },
+          {
+            key: 'settings',
+            icon: <SettingOutlined />,
+            label: '个人设置',
+          },
+          {
+            type: 'divider' as const,
+          },
+        ]
+      : []),
+    {
+      key: 'logout',
+      icon: <LogoutOutlined />,
+      label: '退出登录',
+    },
+  ];
+
+  return (
+    <HeaderDropdown
+      menu={{
+        selectedKeys: [],
+        onClick: onMenuClick,
+        items: menuItems,
+      }}
+    >
+      {children}
+    </HeaderDropdown>
+  );
+};
diff --git a/react-ui/src/components/RightContent/index.tsx b/react-ui/src/components/RightContent/index.tsx
new file mode 100644
index 0000000..20a7831
--- /dev/null
+++ b/react-ui/src/components/RightContent/index.tsx
@@ -0,0 +1,31 @@
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import { SelectLang as UmiSelectLang } from '@umijs/max';
+import React from 'react';
+
+export type SiderTheme = 'light' | 'dark';
+
+export const SelectLang = () => {
+  return (
+    <UmiSelectLang
+      style={{
+        padding: 4,
+      }}
+    />
+  );
+};
+
+export const Question = () => {
+  return (
+    <div
+      style={{
+        display: 'flex',
+        height: 26,
+      }}
+      onClick={() => {
+        window.open('https://pro.ant.design/docs/getting-started');
+      }}
+    >
+      <QuestionCircleOutlined />
+    </div>
+  );
+};
diff --git a/react-ui/src/components/index.ts b/react-ui/src/components/index.ts
new file mode 100644
index 0000000..ca88a6d
--- /dev/null
+++ b/react-ui/src/components/index.ts
@@ -0,0 +1,12 @@
+/**
+ * 这个文件作为组件的目录
+ * 目的是统一管理对外输出的组件,方便分类
+ */
+/**
+ * 布局组件
+ */
+import Footer from './Footer';
+import { Question, SelectLang } from './RightContent';
+import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown';
+
+export { Footer, Question, SelectLang, AvatarDropdown, AvatarName };
diff --git a/react-ui/src/enums/httpEnum.ts b/react-ui/src/enums/httpEnum.ts
new file mode 100644
index 0000000..8b7ff1f
--- /dev/null
+++ b/react-ui/src/enums/httpEnum.ts
@@ -0,0 +1,31 @@
+/**
+ * @description: Request result set
+ */
+export enum HttpResult {
+  SUCCESS = 200,
+  ERROR = -1,
+  TIMEOUT = 401,
+  TYPE = 'success',
+}
+
+/**
+ * @description: request method
+ */
+export enum RequestMethod {
+  GET = 'GET',
+  POST = 'POST',
+  PUT = 'PUT',
+  DELETE = 'DELETE',
+}
+
+/**
+ * @description:  contentType
+ */
+export enum ContentType {
+  // json
+  JSON = 'application/json;charset=UTF-8',
+  // form-data qs
+  FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
+  // form-data  upload
+  FORM_DATA = 'multipart/form-data;charset=UTF-8',
+}
diff --git a/react-ui/src/enums/pagesEnums.ts b/react-ui/src/enums/pagesEnums.ts
new file mode 100644
index 0000000..4bdb5fd
--- /dev/null
+++ b/react-ui/src/enums/pagesEnums.ts
@@ -0,0 +1,4 @@
+
+export enum PageEnum {
+    LOGIN = '/user/login'
+}
\ No newline at end of file
diff --git a/react-ui/src/global.less b/react-ui/src/global.less
new file mode 100644
index 0000000..a9a0c51
--- /dev/null
+++ b/react-ui/src/global.less
@@ -0,0 +1,53 @@
+html,
+body,
+#root {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
+    'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
+    'Noto Color Emoji';
+}
+
+.colorWeak {
+  filter: invert(80%);
+}
+
+.ant-layout {
+  min-height: 100vh;
+}
+.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
+  left: unset;
+}
+
+canvas {
+  display: block;
+}
+
+body {
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+ul,
+ol {
+  list-style: none;
+}
+
+@media (max-width: 768px) {
+  .ant-table {
+    width: 100%;
+    overflow-x: auto;
+    &-thead > tr,
+    &-tbody > tr {
+      > th,
+      > td {
+        white-space: pre;
+        > span {
+          display: block;
+        }
+      }
+    }
+  }
+}
diff --git a/react-ui/src/global.tsx b/react-ui/src/global.tsx
new file mode 100644
index 0000000..afa1fab
--- /dev/null
+++ b/react-ui/src/global.tsx
@@ -0,0 +1,91 @@
+import { useIntl } from '@umijs/max';
+import { Button, message, notification } from 'antd';
+import defaultSettings from '../config/defaultSettings';
+
+const { pwa } = defaultSettings;
+const isHttps = document.location.protocol === 'https:';
+
+const clearCache = () => {
+  // remove all caches
+  if (window.caches) {
+    caches
+      .keys()
+      .then((keys) => {
+        keys.forEach((key) => {
+          caches.delete(key);
+        });
+      })
+      .catch((e) => console.log(e));
+  }
+};
+
+// if pwa is true
+if (pwa) {
+  // Notify user if offline now
+  window.addEventListener('sw.offline', () => {
+    message.warning(useIntl().formatMessage({ id: 'app.pwa.offline' }));
+  });
+
+  // Pop up a prompt on the page asking the user if they want to use the latest version
+  window.addEventListener('sw.updated', (event: Event) => {
+    const e = event as CustomEvent;
+    const reloadSW = async () => {
+      // Check if there is sw whose state is waiting in ServiceWorkerRegistration
+      // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
+      const worker = e.detail && e.detail.waiting;
+      if (!worker) {
+        return true;
+      }
+      // Send skip-waiting event to waiting SW with MessageChannel
+      await new Promise((resolve, reject) => {
+        const channel = new MessageChannel();
+        channel.port1.onmessage = (msgEvent) => {
+          if (msgEvent.data.error) {
+            reject(msgEvent.data.error);
+          } else {
+            resolve(msgEvent.data);
+          }
+        };
+        worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
+      });
+
+      clearCache();
+      window.location.reload();
+      return true;
+    };
+    const key = `open${Date.now()}`;
+    const btn = (
+      <Button
+        type="primary"
+        onClick={() => {
+          notification.destroy(key);
+          reloadSW();
+        }}
+      >
+        {useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
+      </Button>
+    );
+    notification.open({
+      message: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }),
+      description: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
+      btn,
+      key,
+      onClose: async () => null,
+    });
+  });
+} else if ('serviceWorker' in navigator && isHttps) {
+  // unregister service worker
+  const { serviceWorker } = navigator;
+  if (serviceWorker.getRegistrations) {
+    serviceWorker.getRegistrations().then((sws) => {
+      sws.forEach((sw) => {
+        sw.unregister();
+      });
+    });
+  }
+  serviceWorker.getRegistration().then((sw) => {
+    if (sw) sw.unregister();
+  });
+
+  clearCache();
+}
diff --git a/react-ui/src/hooks/net/dict.ts b/react-ui/src/hooks/net/dict.ts
new file mode 100644
index 0000000..9c2c41c
--- /dev/null
+++ b/react-ui/src/hooks/net/dict.ts
@@ -0,0 +1,7 @@
+import { getDictValueEnum } from "@/services/system/dict";
+
+export function useDictEnum(name: string)
+{
+    const data = getDictValueEnum(name);
+    return data;
+}
\ No newline at end of file
diff --git a/react-ui/src/locales/en-US.ts b/react-ui/src/locales/en-US.ts
new file mode 100644
index 0000000..58407d2
--- /dev/null
+++ b/react-ui/src/locales/en-US.ts
@@ -0,0 +1,27 @@
+import app from './en-US/app';
+import component from './en-US/component';
+import globalHeader from './en-US/globalHeader';
+import menu from './en-US/menu';
+import pages from './en-US/pages';
+import pwa from './en-US/pwa';
+import settingDrawer from './en-US/settingDrawer';
+import settings from './en-US/settings';
+
+export default {
+  'navBar.lang': 'Languages',
+  'layout.user.link.help': 'Help',
+  'layout.user.link.privacy': 'Privacy',
+  'layout.user.link.terms': 'Terms',
+  'app.copyright.produced': 'Produced by Ant Financial Experience Department',
+  'app.preview.down.block': 'Download this page to your local project',
+  'app.welcome.link.fetch-blocks': 'Get all block',
+  'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
+  ...app,
+  ...globalHeader,
+  ...menu,
+  ...settingDrawer,
+  ...settings,
+  ...pwa,
+  ...component,
+  ...pages,
+};
diff --git a/react-ui/src/locales/en-US/app.ts b/react-ui/src/locales/en-US/app.ts
new file mode 100644
index 0000000..074dcb8
--- /dev/null
+++ b/react-ui/src/locales/en-US/app.ts
@@ -0,0 +1,26 @@
+export default {
+	'app.docs.components.icon.search.placeholder': 'Search icons here, click icon to copy code',
+	'app.docs.components.icon.outlined': 'Outlined',
+	'app.docs.components.icon.filled': 'Filled',
+	'app.docs.components.icon.two-tone': 'Two Tone',
+	'app.docs.components.icon.category.direction': 'Directional Icons',
+	'app.docs.components.icon.category.suggestion': 'Suggested Icons',
+	'app.docs.components.icon.category.editor': 'Editor Icons',
+	'app.docs.components.icon.category.data': 'Data Icons',
+	'app.docs.components.icon.category.other': 'Application Icons',
+	'app.docs.components.icon.category.logo': 'Brand and Logos',
+	'app.docs.components.icon.pic-searcher.intro':
+		'AI Search by image is online, you are welcome to use it! 🎉',
+	'app.docs.components.icon.pic-searcher.title': 'Search by image',
+	'app.docs.components.icon.pic-searcher.upload-text':
+		'Click, drag, or paste file to this area to upload',
+	'app.docs.components.icon.pic-searcher.upload-hint':
+		'We will find the best matching icon based on the image provided',
+	'app.docs.components.icon.pic-searcher.server-error':
+		'Predict service is temporarily unavailable',
+	'app.docs.components.icon.pic-searcher.matching': 'Matching...',
+	'app.docs.components.icon.pic-searcher.modelloading': 'Model is loading...',
+	'app.docs.components.icon.pic-searcher.result-tip': 'Matched the following icons for you:',
+	'app.docs.components.icon.pic-searcher.th-icon': 'Icon',
+	'app.docs.components.icon.pic-searcher.th-score': 'Probability',
+};
diff --git a/react-ui/src/locales/en-US/component.ts b/react-ui/src/locales/en-US/component.ts
new file mode 100644
index 0000000..3ba7eed
--- /dev/null
+++ b/react-ui/src/locales/en-US/component.ts
@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': 'Expand',
+  'component.tagSelect.collapse': 'Collapse',
+  'component.tagSelect.all': 'All',
+};
diff --git a/react-ui/src/locales/en-US/globalHeader.ts b/react-ui/src/locales/en-US/globalHeader.ts
new file mode 100644
index 0000000..60b6d4e
--- /dev/null
+++ b/react-ui/src/locales/en-US/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+  'component.globalHeader.search': 'Search',
+  'component.globalHeader.search.example1': 'Search example 1',
+  'component.globalHeader.search.example2': 'Search example 2',
+  'component.globalHeader.search.example3': 'Search example 3',
+  'component.globalHeader.help': 'Help',
+  'component.globalHeader.notification': 'Notification',
+  'component.globalHeader.notification.empty': 'You have viewed all notifications.',
+  'component.globalHeader.message': 'Message',
+  'component.globalHeader.message.empty': 'You have viewed all messsages.',
+  'component.globalHeader.event': 'Event',
+  'component.globalHeader.event.empty': 'You have viewed all events.',
+  'component.noticeIcon.clear': 'Clear',
+  'component.noticeIcon.cleared': 'Cleared',
+  'component.noticeIcon.empty': 'No notifications',
+  'component.noticeIcon.view-more': 'View more',
+};
diff --git a/react-ui/src/locales/en-US/menu.ts b/react-ui/src/locales/en-US/menu.ts
new file mode 100644
index 0000000..eae3e53
--- /dev/null
+++ b/react-ui/src/locales/en-US/menu.ts
@@ -0,0 +1,52 @@
+export default {
+  'menu.welcome': 'Welcome',
+  'menu.more-blocks': 'More Blocks',
+  'menu.home': 'Home',
+  'menu.admin': 'Admin',
+  'menu.admin.sub-page': 'Sub-Page',
+  'menu.login': 'Login',
+  'menu.register': 'Register',
+  'menu.register-result': 'Register Result',
+  'menu.dashboard': 'Dashboard',
+  'menu.dashboard.analysis': 'Analysis',
+  'menu.dashboard.monitor': 'Monitor',
+  'menu.dashboard.workplace': 'Workplace',
+  'menu.exception.403': '403',
+  'menu.exception.404': '404',
+  'menu.exception.500': '500',
+  'menu.form': 'Form',
+  'menu.form.basic-form': 'Basic Form',
+  'menu.form.step-form': 'Step Form',
+  'menu.form.step-form.info': 'Step Form(write transfer information)',
+  'menu.form.step-form.confirm': 'Step Form(confirm transfer information)',
+  'menu.form.step-form.result': 'Step Form(finished)',
+  'menu.form.advanced-form': 'Advanced Form',
+  'menu.list': 'List',
+  'menu.list.table-list': 'Search Table',
+  'menu.list.basic-list': 'Basic List',
+  'menu.list.card-list': 'Card List',
+  'menu.list.search-list': 'Search List',
+  'menu.list.search-list.articles': 'Search List(articles)',
+  'menu.list.search-list.projects': 'Search List(projects)',
+  'menu.list.search-list.applications': 'Search List(applications)',
+  'menu.profile': 'Profile',
+  'menu.profile.basic': 'Basic Profile',
+  'menu.profile.advanced': 'Advanced Profile',
+  'menu.result': 'Result',
+  'menu.result.success': 'Success',
+  'menu.result.fail': 'Fail',
+  'menu.exception': 'Exception',
+  'menu.exception.not-permission': '403',
+  'menu.exception.not-find': '404',
+  'menu.exception.server-error': '500',
+  'menu.exception.trigger': 'Trigger',
+  'menu.account': 'Account',
+  'menu.account.center': 'Account Center',
+  'menu.account.settings': 'Account Settings',
+  'menu.account.trigger': 'Trigger Error',
+  'menu.account.logout': 'Logout',
+  'menu.editor': 'Graphic Editor',
+  'menu.editor.flow': 'Flow Editor',
+  'menu.editor.mind': 'Mind Editor',
+  'menu.editor.koni': 'Koni Editor',
+};
diff --git a/react-ui/src/locales/en-US/pages.ts b/react-ui/src/locales/en-US/pages.ts
new file mode 100644
index 0000000..58ff2c8
--- /dev/null
+++ b/react-ui/src/locales/en-US/pages.ts
@@ -0,0 +1,71 @@
+export default {
+  'pages.layouts.userLayout.title':
+    'Ant Design is the most influential web design specification in Xihu district',
+  'pages.login.accountLogin.tab': 'Account Login',
+  'pages.login.accountLogin.errorMessage': 'Incorrect username/password(admin/admin123)',
+  'pages.login.failure': 'Login failed, please try again!',
+  'pages.login.success': 'Login successful!',
+  'pages.login.username.placeholder': 'Username: admin',
+  'pages.login.username.required': 'Please input your username!',
+  'pages.login.password.placeholder': 'Password: admin123',
+  'pages.login.password.required': 'Please input your password!',
+  'pages.login.phoneLogin.tab': 'Phone Login',
+  'pages.login.phoneLogin.errorMessage': 'Verification Code Error',
+  'pages.login.phoneNumber.placeholder': 'Phone Number',
+  'pages.login.phoneNumber.required': 'Please input your phone number!',
+  'pages.login.phoneNumber.invalid': 'Phone number is invalid!',
+  'pages.login.captcha.placeholder': 'Verification Code',
+  'pages.login.captcha.required': 'Please input verification code!',
+  'pages.login.phoneLogin.getVerificationCode': 'Get Code',
+  'pages.getCaptchaSecondText': 'sec(s)',
+  'pages.login.rememberMe': 'Remember me',
+  'pages.login.forgotPassword': 'Forgot Password ?',
+  'pages.login.submit': 'Login',
+  'pages.login.loginWith': 'Login with :',
+  'pages.login.registerAccount': 'Register Account',
+  'pages.welcome.link': 'Welcome',
+  'pages.welcome.alertMessage': 'Faster and stronger heavy-duty components have been released.',
+  'pages.admin.subPage.title': 'This page can only be viewed by Admin',
+  'pages.admin.subPage.alertMessage':
+    'Umi ui is now released, welcome to use npm run ui to start the experience.',
+  'pages.searchTable.createForm.newRule': 'New Rule',
+  'pages.searchTable.updateForm.ruleConfig': 'Rule configuration',
+  'pages.searchTable.updateForm.basicConfig': 'Basic Information',
+  'pages.searchTable.updateForm.ruleName.nameLabel': 'Rule Name',
+  'pages.searchTable.updateForm.ruleName.nameRules': 'Please enter the rule name!',
+  'pages.searchTable.updateForm.ruleDesc.descLabel': 'Rule Description',
+  'pages.searchTable.updateForm.ruleDesc.descPlaceholder': 'Please enter at least five characters',
+  'pages.searchTable.updateForm.ruleDesc.descRules':
+    'Please enter a rule description of at least five characters!',
+  'pages.searchTable.updateForm.ruleProps.title': 'Configure Properties',
+  'pages.searchTable.updateForm.object': 'Monitoring Object',
+  'pages.searchTable.updateForm.ruleProps.templateLabel': 'Rule Template',
+  'pages.searchTable.updateForm.ruleProps.typeLabel': 'Rule Type',
+  'pages.searchTable.updateForm.schedulingPeriod.title': 'Set Scheduling Period',
+  'pages.searchTable.updateForm.schedulingPeriod.timeLabel': 'Starting Time',
+  'pages.searchTable.updateForm.schedulingPeriod.timeRules': 'Please choose a start time!',
+  'pages.searchTable.titleDesc': 'Description',
+  'pages.searchTable.ruleName': 'Rule name is required',
+  'pages.searchTable.titleCallNo': 'Number of Service Calls',
+  'pages.searchTable.titleStatus': 'Status',
+  'pages.searchTable.nameStatus.default': 'default',
+  'pages.searchTable.nameStatus.running': 'running',
+  'pages.searchTable.nameStatus.online': 'online',
+  'pages.searchTable.nameStatus.abnormal': 'abnormal',
+  'pages.searchTable.titleUpdatedAt': 'Last Scheduled at',
+  'pages.searchTable.exception': 'Please enter the reason for the exception!',
+  'pages.searchTable.titleOption': 'Option',
+  'pages.searchTable.config': 'Configuration',
+  'pages.searchTable.subscribeAlert': 'Subscribe to alerts',
+  'pages.searchTable.title': 'Enquiry Form',
+  'pages.searchTable.new': 'New',
+  'pages.searchTable.edit': 'Edit',
+  'pages.searchTable.delete': 'Delete',
+  'pages.searchTable.export': 'Export',
+  'pages.searchTable.chosen': 'chosen',
+  'pages.searchTable.item': 'item',
+  'pages.searchTable.totalServiceCalls': 'Total Number of Service Calls',
+  'pages.searchTable.tenThousand': '0000',
+  'pages.searchTable.batchDeletion': 'batch deletion',
+  'pages.searchTable.batchApproval': 'batch approval',
+};
diff --git a/react-ui/src/locales/en-US/pwa.ts b/react-ui/src/locales/en-US/pwa.ts
new file mode 100644
index 0000000..ed8d199
--- /dev/null
+++ b/react-ui/src/locales/en-US/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': 'You are offline now',
+  'app.pwa.serviceworker.updated': 'New content is available',
+  'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
+  'app.pwa.serviceworker.updated.ok': 'Refresh',
+};
diff --git a/react-ui/src/locales/en-US/settingDrawer.ts b/react-ui/src/locales/en-US/settingDrawer.ts
new file mode 100644
index 0000000..a644905
--- /dev/null
+++ b/react-ui/src/locales/en-US/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+  'app.setting.pagestyle': 'Page style setting',
+  'app.setting.pagestyle.dark': 'Dark style',
+  'app.setting.pagestyle.light': 'Light style',
+  'app.setting.content-width': 'Content Width',
+  'app.setting.content-width.fixed': 'Fixed',
+  'app.setting.content-width.fluid': 'Fluid',
+  'app.setting.themecolor': 'Theme Color',
+  'app.setting.themecolor.dust': 'Dust Red',
+  'app.setting.themecolor.volcano': 'Volcano',
+  'app.setting.themecolor.sunset': 'Sunset Orange',
+  'app.setting.themecolor.cyan': 'Cyan',
+  'app.setting.themecolor.green': 'Polar Green',
+  'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
+  'app.setting.themecolor.geekblue': 'Geek Glue',
+  'app.setting.themecolor.purple': 'Golden Purple',
+  'app.setting.navigationmode': 'Navigation Mode',
+  'app.setting.sidemenu': 'Side Menu Layout',
+  'app.setting.topmenu': 'Top Menu Layout',
+  'app.setting.fixedheader': 'Fixed Header',
+  'app.setting.fixedsidebar': 'Fixed Sidebar',
+  'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout',
+  'app.setting.hideheader': 'Hidden Header when scrolling',
+  'app.setting.hideheader.hint': 'Works when Hidden Header is enabled',
+  'app.setting.othersettings': 'Other Settings',
+  'app.setting.weakmode': 'Weak Mode',
+  'app.setting.copy': 'Copy Setting',
+  'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
+  'app.setting.production.hint':
+    'Setting panel shows in development environment only, please manually modify',
+};
diff --git a/react-ui/src/locales/en-US/settings.ts b/react-ui/src/locales/en-US/settings.ts
new file mode 100644
index 0000000..822dd00
--- /dev/null
+++ b/react-ui/src/locales/en-US/settings.ts
@@ -0,0 +1,60 @@
+export default {
+  'app.settings.menuMap.basic': 'Basic Settings',
+  'app.settings.menuMap.security': 'Security Settings',
+  'app.settings.menuMap.binding': 'Account Binding',
+  'app.settings.menuMap.notification': 'New Message Notification',
+  'app.settings.basic.avatar': 'Avatar',
+  'app.settings.basic.change-avatar': 'Change avatar',
+  'app.settings.basic.email': 'Email',
+  'app.settings.basic.email-message': 'Please input your email!',
+  'app.settings.basic.nickname': 'Nickname',
+  'app.settings.basic.nickname-message': 'Please input your Nickname!',
+  'app.settings.basic.profile': 'Personal profile',
+  'app.settings.basic.profile-message': 'Please input your personal profile!',
+  'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
+  'app.settings.basic.country': 'Country/Region',
+  'app.settings.basic.country-message': 'Please input your country!',
+  'app.settings.basic.geographic': 'Province or city',
+  'app.settings.basic.geographic-message': 'Please input your geographic info!',
+  'app.settings.basic.address': 'Street Address',
+  'app.settings.basic.address-message': 'Please input your address!',
+  'app.settings.basic.phone': 'Phone Number',
+  'app.settings.basic.phone-message': 'Please input your phone!',
+  'app.settings.basic.update': 'Update Information',
+  'app.settings.security.strong': 'Strong',
+  'app.settings.security.medium': 'Medium',
+  'app.settings.security.weak': 'Weak',
+  'app.settings.security.password': 'Account Password',
+  'app.settings.security.password-description': 'Current password strength',
+  'app.settings.security.phone': 'Security Phone',
+  'app.settings.security.phone-description': 'Bound phone',
+  'app.settings.security.question': 'Security Question',
+  'app.settings.security.question-description':
+    'The security question is not set, and the security policy can effectively protect the account security',
+  'app.settings.security.email': 'Backup Email',
+  'app.settings.security.email-description': 'Bound Email',
+  'app.settings.security.mfa': 'MFA Device',
+  'app.settings.security.mfa-description':
+    'Unbound MFA device, after binding, can be confirmed twice',
+  'app.settings.security.modify': 'Modify',
+  'app.settings.security.set': 'Set',
+  'app.settings.security.bind': 'Bind',
+  'app.settings.binding.taobao': 'Binding Taobao',
+  'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
+  'app.settings.binding.alipay': 'Binding Alipay',
+  'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
+  'app.settings.binding.dingding': 'Binding DingTalk',
+  'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
+  'app.settings.binding.bind': 'Bind',
+  'app.settings.notification.password': 'Account Password',
+  'app.settings.notification.password-description':
+    'Messages from other users will be notified in the form of a station letter',
+  'app.settings.notification.messages': 'System Messages',
+  'app.settings.notification.messages-description':
+    'System messages will be notified in the form of a station letter',
+  'app.settings.notification.todo': 'To-do Notification',
+  'app.settings.notification.todo-description':
+    'The to-do list will be notified in the form of a letter from the station',
+  'app.settings.open': 'Open',
+  'app.settings.close': 'Close',
+};
diff --git a/react-ui/src/locales/zh-CN.ts b/react-ui/src/locales/zh-CN.ts
new file mode 100644
index 0000000..06e2eac
--- /dev/null
+++ b/react-ui/src/locales/zh-CN.ts
@@ -0,0 +1,57 @@
+import app from './zh-CN/app';
+import component from './zh-CN/component';
+import globalHeader from './zh-CN/globalHeader';
+import sysmenu from './zh-CN/menu';
+import pages from './zh-CN/pages';
+import pwa from './zh-CN/pwa';
+import settingDrawer from './zh-CN/settingDrawer';
+import settings from './zh-CN/settings';
+import user from './zh-CN/system/user';
+import menu from './zh-CN/system/menu';
+import dict from './zh-CN/system/dict';
+import dictData from './zh-CN/system/dict-data';
+import role from './zh-CN/system/role';
+import dept from './zh-CN/system/dept';
+import post from './zh-CN/system/post';
+import config from './zh-CN/system/config';
+import notice from './zh-CN/system/notice';
+import operlog from './zh-CN/monitor/operlog';
+import logininfor from './zh-CN/monitor/logininfor';
+import onlineUser from './zh-CN/monitor/onlineUser';
+import job from './zh-CN/monitor/job';
+import joblog from './zh-CN/monitor/job-log';
+import server from './zh-CN/monitor/server';
+
+export default {
+  'navBar.lang': '语言',
+  'layout.user.link.help': '帮助',
+  'layout.user.link.privacy': '隐私',
+  'layout.user.link.terms': '条款',
+  'app.copyright.produced': '蚂蚁集团体验技术部出品',
+  'app.preview.down.block': '下载此页面到本地项目',
+  'app.welcome.link.fetch-blocks': '获取全部区块',
+  'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
+  ...app,
+  ...pages,
+  ...globalHeader,
+  ...sysmenu,
+  ...settingDrawer,
+  ...settings,
+  ...pwa,
+  ...component,
+  ...user,
+  ...menu,
+  ...dict,
+  ...dictData,
+  ...role,
+  ...dept,
+  ...post,
+  ...config,
+  ...notice,
+  ...operlog,
+  ...logininfor,
+  ...onlineUser,
+  ...job,
+  ...joblog,
+  ...server,
+};
diff --git a/react-ui/src/locales/zh-CN/app.ts b/react-ui/src/locales/zh-CN/app.ts
new file mode 100644
index 0000000..5be68e2
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/app.ts
@@ -0,0 +1,23 @@
+export default {
+  'app.docs.components.icon.search.placeholder': '在此搜索图标,点击图标可复制代码',
+  'app.docs.components.icon.outlined': '线框风格',
+  'app.docs.components.icon.filled': '实底风格',
+  'app.docs.components.icon.two-tone': '双色风格',
+  'app.docs.components.icon.category.direction': '方向性图标',
+  'app.docs.components.icon.category.suggestion': '提示建议性图标',
+  'app.docs.components.icon.category.editor': '编辑类图标',
+  'app.docs.components.icon.category.data': '数据类图标',
+  'app.docs.components.icon.category.other': '网站通用图标',
+  'app.docs.components.icon.category.logo': '品牌和标识',
+  'app.docs.components.icon.pic-searcher.intro': 'AI 截图搜索上线了,快来体验吧!🎉',
+  'app.docs.components.icon.pic-searcher.title': '上传图片搜索图标',
+  'app.docs.components.icon.pic-searcher.upload-text': '点击/拖拽/粘贴上传图片',
+  'app.docs.components.icon.pic-searcher.upload-hint':
+    '我们会通过上传的图片进行匹配,得到最相似的图标',
+  'app.docs.components.icon.pic-searcher.server-error': '识别服务暂不可用',
+  'app.docs.components.icon.pic-searcher.matching': '匹配中...',
+  'app.docs.components.icon.pic-searcher.modelloading': '神经网络模型加载中...',
+  'app.docs.components.icon.pic-searcher.result-tip': '为您匹配到以下图标:',
+  'app.docs.components.icon.pic-searcher.th-icon': '图标',
+  'app.docs.components.icon.pic-searcher.th-score': '匹配度',
+};
diff --git a/react-ui/src/locales/zh-CN/component.ts b/react-ui/src/locales/zh-CN/component.ts
new file mode 100644
index 0000000..1f1fead
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/component.ts
@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': '展开',
+  'component.tagSelect.collapse': '收起',
+  'component.tagSelect.all': '全部',
+};
diff --git a/react-ui/src/locales/zh-CN/globalHeader.ts b/react-ui/src/locales/zh-CN/globalHeader.ts
new file mode 100644
index 0000000..9fd66a5
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+  'component.globalHeader.search': '站内搜索',
+  'component.globalHeader.search.example1': '搜索提示一',
+  'component.globalHeader.search.example2': '搜索提示二',
+  'component.globalHeader.search.example3': '搜索提示三',
+  'component.globalHeader.help': '使用文档',
+  'component.globalHeader.notification': '通知',
+  'component.globalHeader.notification.empty': '你已查看所有通知',
+  'component.globalHeader.message': '消息',
+  'component.globalHeader.message.empty': '您已读完所有消息',
+  'component.globalHeader.event': '待办',
+  'component.globalHeader.event.empty': '你已完成所有待办',
+  'component.noticeIcon.clear': '清空',
+  'component.noticeIcon.cleared': '清空了',
+  'component.noticeIcon.empty': '暂无数据',
+  'component.noticeIcon.view-more': '查看更多',
+};
diff --git a/react-ui/src/locales/zh-CN/menu.ts b/react-ui/src/locales/zh-CN/menu.ts
new file mode 100644
index 0000000..fecb70a
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/menu.ts
@@ -0,0 +1,52 @@
+export default {
+  'menu.welcome': '欢迎',
+  'menu.more-blocks': '更多区块',
+  'menu.home': '首页',
+  'menu.admin': '管理页',
+  'menu.admin.sub-page': '二级管理页',
+  'menu.login': '登录',
+  'menu.register': '注册',
+  'menu.register-result': '注册结果',
+  'menu.dashboard': 'Dashboard',
+  'menu.dashboard.analysis': '分析页',
+  'menu.dashboard.monitor': '监控页',
+  'menu.dashboard.workplace': '工作台',
+  'menu.exception.403': '403',
+  'menu.exception.404': '404',
+  'menu.exception.500': '500',
+  'menu.form': '表单页',
+  'menu.form.basic-form': '基础表单',
+  'menu.form.step-form': '分步表单',
+  'menu.form.step-form.info': '分步表单(填写转账信息)',
+  'menu.form.step-form.confirm': '分步表单(确认转账信息)',
+  'menu.form.step-form.result': '分步表单(完成)',
+  'menu.form.advanced-form': '高级表单',
+  'menu.list': '列表页',
+  'menu.list.table-list': '查询表格',
+  'menu.list.basic-list': '标准列表',
+  'menu.list.card-list': '卡片列表',
+  'menu.list.search-list': '搜索列表',
+  'menu.list.search-list.articles': '搜索列表(文章)',
+  'menu.list.search-list.projects': '搜索列表(项目)',
+  'menu.list.search-list.applications': '搜索列表(应用)',
+  'menu.profile': '详情页',
+  'menu.profile.basic': '基础详情页',
+  'menu.profile.advanced': '高级详情页',
+  'menu.result': '结果页',
+  'menu.result.success': '成功页',
+  'menu.result.fail': '失败页',
+  'menu.exception': '异常页',
+  'menu.exception.not-permission': '403',
+  'menu.exception.not-find': '404',
+  'menu.exception.server-error': '500',
+  'menu.exception.trigger': '触发错误',
+  'menu.account': '个人页',
+  'menu.account.center': '个人中心',
+  'menu.account.settings': '个人设置',
+  'menu.account.trigger': '触发报错',
+  'menu.account.logout': '退出登录',
+  'menu.editor': '图形编辑器',
+  'menu.editor.flow': '流程编辑器',
+  'menu.editor.mind': '脑图编辑器',
+  'menu.editor.koni': '拓扑编辑器',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/job-log.ts b/react-ui/src/locales/zh-CN/monitor/job-log.ts
new file mode 100644
index 0000000..75cf8d0
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/job-log.ts
@@ -0,0 +1,18 @@
+/**
+ * 定时任务调度日志
+ * 
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+export default {
+	'monitor.job.log.title': '定时任务调度日志',
+	'monitor.job.log.job_log_id': '任务日志编号',
+	'monitor.job.log.job_name': '任务名称',
+	'monitor.job.log.job_group': '任务组名',
+	'monitor.job.log.invoke_target': '调用方法',
+	'monitor.job.log.job_message': '日志信息',
+	'monitor.job.log.status': '执行状态',
+	'monitor.job.log.exception_info': '异常信息',
+	'monitor.job.log.create_time': '创建时间',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/job.ts b/react-ui/src/locales/zh-CN/monitor/job.ts
new file mode 100644
index 0000000..7e0d5b9
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/job.ts
@@ -0,0 +1,25 @@
+/**
+ * 定时任务调度
+ * 
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+export default {
+	'monitor.job.title': '定时任务调度',
+	'monitor.job.job_id': '任务编号',
+	'monitor.job.job_name': '任务名称',
+	'monitor.job.job_group': '任务组名',
+	'monitor.job.invoke_target': '调用方法',
+	'monitor.job.cron_expression': 'cron执行表达式',
+	'monitor.job.misfire_policy': '执行策略',
+	'monitor.job.concurrent': '是否并发执行',
+	'monitor.job.next_valid_time': '下次执行时间',
+	'monitor.job.status': '状态',
+	'monitor.job.create_by': '创建者',
+	'monitor.job.create_time': '创建时间',
+	'monitor.job.update_by': '更新者',
+	'monitor.job.update_time': '更新时间',
+	'monitor.job.remark': '备注信息',
+	'monitor.job.detail': '任务详情',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/logininfor.ts b/react-ui/src/locales/zh-CN/monitor/logininfor.ts
new file mode 100644
index 0000000..959a6d2
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/logininfor.ts
@@ -0,0 +1,13 @@
+export default {
+	'monitor.logininfor.title': '系统访问记录',
+	'monitor.logininfor.info_id': '访问编号',
+	'monitor.logininfor.user_name': '用户账号',
+	'monitor.logininfor.ipaddr': '登录IP地址',
+	'monitor.logininfor.login_location': '登录地点',
+	'monitor.logininfor.browser': '浏览器类型',
+	'monitor.logininfor.os': '操作系统',
+	'monitor.logininfor.status': '登录状态',
+	'monitor.logininfor.msg': '提示消息',
+	'monitor.logininfor.login_time': '访问时间',
+	'monitor.logininfor.unlock': '解锁',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/onlineUser.ts b/react-ui/src/locales/zh-CN/monitor/onlineUser.ts
new file mode 100644
index 0000000..c693cde
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/onlineUser.ts
@@ -0,0 +1,20 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+export default {
+  'monitor.online.user.id': '编号',
+  'monitor.online.user.token_id': '会话编号',
+  'monitor.online.user.user_name': '会话编号',
+  'monitor.online.user.ipaddr': '登录IP地址',
+  'monitor.online.user.login_location': '登录地点',
+  'monitor.online.user.browser': '浏览器类型',
+  'monitor.online.user.os': '操作系统',
+  'monitor.online.user.dept_name': '部门',
+  'monitor.online.user.login_time': '访问时间',
+  'monitor.online.user.force_logout': '强制退出',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/operlog.ts b/react-ui/src/locales/zh-CN/monitor/operlog.ts
new file mode 100644
index 0000000..c45824c
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/operlog.ts
@@ -0,0 +1,19 @@
+export default {
+	'monitor.operlog.title': '操作日志记录',
+	'monitor.operlog.oper_id': '日志主键',
+	'monitor.operlog.business_type': '业务类型',
+	'monitor.operlog.method': '方法名称',
+	'monitor.operlog.request_method': '请求方式',
+	'monitor.operlog.operator_type': '操作类别',
+	'monitor.operlog.oper_name': '操作人员',
+	'monitor.operlog.dept_name': '部门名称',
+	'monitor.operlog.oper_url': '请求URL',
+	'monitor.operlog.oper_ip': '主机地址',
+	'monitor.operlog.oper_location': '操作地点',
+	'monitor.operlog.oper_param': '请求参数',
+	'monitor.operlog.json_result': '返回参数',
+	'monitor.operlog.status': '操作状态',
+	'monitor.operlog.error_msg': '错误消息',
+	'monitor.operlog.oper_time': '操作时间',
+	'monitor.operlog.module': '操作模块',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/server.ts b/react-ui/src/locales/zh-CN/monitor/server.ts
new file mode 100644
index 0000000..f887a22
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/server.ts
@@ -0,0 +1,27 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+export default {
+  'monitor.server.cpu.cpuNum': '核心数',
+  'monitor.server.cpu.total': '总使用率',
+  'monitor.server.cpu.sys': '系统使用率',
+  'monitor.server.cpu.used': '用户使用率',
+  'monitor.server.cpu.wait': 'IO等待',
+  'monitor.server.cpu.free': '当前空闲率',
+  'monitor.server.mem.total': '总内存',
+  'monitor.server.mem.used': '已用内存',
+  'monitor.server.mem.free': '剩余内存',
+  'monitor.server.mem.usage': '使用率',
+  'monitor.server.disk.dirName': '盘符路径',
+  'monitor.server.disk.sysTypeName': '文件系统',
+  'monitor.server.disk.typeName': '盘符类型',
+  'monitor.server.disk.total': '总大小',
+  'monitor.server.disk.free': '可用大小',
+  'monitor.server.disk.used': '已用大小',
+  'monitor.server.disk.usage': '使用率',
+};
diff --git a/react-ui/src/locales/zh-CN/pages.ts b/react-ui/src/locales/zh-CN/pages.ts
new file mode 100644
index 0000000..fc7abfb
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/pages.ts
@@ -0,0 +1,71 @@
+export default {
+  'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范',
+  'pages.login.accountLogin.tab': '账户密码登录',
+  'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/admin123)',
+  'pages.login.failure': '登录失败,请重试!',
+  'pages.login.success': '登录成功!',
+  'pages.login.username.placeholder': '用户名: admin',
+  'pages.login.username.required': '用户名是必填项!',
+  'pages.login.password.placeholder': '密码: admin123',
+  'pages.login.password.required': '密码是必填项!',
+  'pages.login.phoneLogin.tab': '手机号登录',
+  'pages.login.phoneLogin.errorMessage': '验证码错误',
+  'pages.login.phoneNumber.placeholder': '请输入手机号!',
+  'pages.login.phoneNumber.required': '手机号是必填项!',
+  'pages.login.phoneNumber.invalid': '不合法的手机号!',
+  'pages.login.captcha.placeholder': '请输入验证码!',
+  'pages.login.captcha.required': '验证码是必填项!',
+  'pages.login.phoneLogin.getVerificationCode': '获取验证码',
+  'pages.getCaptchaSecondText': '秒后重新获取',
+  'pages.login.rememberMe': '自动登录',
+  'pages.login.forgotPassword': '忘记密码 ?',
+  'pages.login.submit': '登录',
+  'pages.login.loginWith': '其他登录方式 :',
+  'pages.login.registerAccount': '注册账户',
+  'pages.goback': '返回',
+  'pages.welcome.link': '欢迎使用',
+  'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。',
+  'pages.admin.subPage.title': ' 这个页面只有 admin 权限才能查看',
+  'pages.admin.subPage.alertMessage': 'umi ui 现已发布,欢迎使用 npm run ui 启动体验。',
+  'pages.searchTable.createForm.newRule': '新建规则',
+  'pages.searchTable.updateForm.ruleConfig': '规则配置',
+  'pages.searchTable.updateForm.basicConfig': '基本信息',
+  'pages.searchTable.updateForm.ruleName.nameLabel': '规则名称',
+  'pages.searchTable.updateForm.ruleName.nameRules': '请输入规则名称!',
+  'pages.searchTable.updateForm.ruleDesc.descLabel': '规则描述',
+  'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '请输入至少五个字符',
+  'pages.searchTable.updateForm.ruleDesc.descRules': '请输入至少五个字符的规则描述!',
+  'pages.searchTable.updateForm.ruleProps.title': '配置规则属性',
+  'pages.searchTable.updateForm.object': '监控对象',
+  'pages.searchTable.updateForm.ruleProps.templateLabel': '规则模板',
+  'pages.searchTable.updateForm.ruleProps.typeLabel': '规则类型',
+  'pages.searchTable.updateForm.schedulingPeriod.title': '设定调度周期',
+  'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '开始时间',
+  'pages.searchTable.updateForm.schedulingPeriod.timeRules': '请选择开始时间!',
+  'pages.searchTable.updateForm.pleaseInput': '请输入',
+  'pages.searchTable.titleDesc': '描述',
+  'pages.searchTable.ruleName': '规则名称为必填项',
+  'pages.searchTable.titleCallNo': '服务调用次数',
+  'pages.searchTable.titleStatus': '状态',
+  'pages.searchTable.nameStatus.default': '关闭',
+  'pages.searchTable.nameStatus.running': '运行中',
+  'pages.searchTable.nameStatus.online': '已上线',
+  'pages.searchTable.nameStatus.abnormal': '异常',
+  'pages.searchTable.titleUpdatedAt': '上次调度时间',
+  'pages.searchTable.exception': '请输入异常原因!',
+  'pages.searchTable.titleOption': '操作',
+  'pages.searchTable.config': '配置',
+  'pages.searchTable.subscribeAlert': '订阅警报',
+  'pages.searchTable.title': '查询表格',
+  'pages.searchTable.new': '新建',
+  'pages.searchTable.edit': '编辑',
+  'pages.searchTable.delete': '删除',
+  'pages.searchTable.cleanAll': '清空',
+  'pages.searchTable.export': '导出',
+  'pages.searchTable.chosen': '已选择',
+  'pages.searchTable.item': '项',
+  'pages.searchTable.totalServiceCalls': '服务调用次数总计',
+  'pages.searchTable.tenThousand': '万',
+  'pages.searchTable.batchDeletion': '批量删除',
+  'pages.searchTable.batchApproval': '批量审批',
+};
diff --git a/react-ui/src/locales/zh-CN/pwa.ts b/react-ui/src/locales/zh-CN/pwa.ts
new file mode 100644
index 0000000..e950484
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': '当前处于离线状态',
+  'app.pwa.serviceworker.updated': '有新内容',
+  'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
+  'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/react-ui/src/locales/zh-CN/settingDrawer.ts b/react-ui/src/locales/zh-CN/settingDrawer.ts
new file mode 100644
index 0000000..3f44958
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+  'app.setting.pagestyle': '整体风格设置',
+  'app.setting.pagestyle.dark': '暗色菜单风格',
+  'app.setting.pagestyle.light': '亮色菜单风格',
+  'app.setting.content-width': '内容区域宽度',
+  'app.setting.content-width.fixed': '定宽',
+  'app.setting.content-width.fluid': '流式',
+  'app.setting.themecolor': '主题色',
+  'app.setting.themecolor.dust': '薄暮',
+  'app.setting.themecolor.volcano': '火山',
+  'app.setting.themecolor.sunset': '日暮',
+  'app.setting.themecolor.cyan': '明青',
+  'app.setting.themecolor.green': '极光绿',
+  'app.setting.themecolor.daybreak': '拂晓蓝(默认)',
+  'app.setting.themecolor.geekblue': '极客蓝',
+  'app.setting.themecolor.purple': '酱紫',
+  'app.setting.navigationmode': '导航模式',
+  'app.setting.sidemenu': '侧边菜单布局',
+  'app.setting.topmenu': '顶部菜单布局',
+  'app.setting.fixedheader': '固定 Header',
+  'app.setting.fixedsidebar': '固定侧边菜单',
+  'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置',
+  'app.setting.hideheader': '下滑时隐藏 Header',
+  'app.setting.hideheader.hint': '固定 Header 时可配置',
+  'app.setting.othersettings': '其他设置',
+  'app.setting.weakmode': '色弱模式',
+  'app.setting.copy': '拷贝设置',
+  'app.setting.copyinfo': '拷贝成功,请到 config/defaultSettings.js 中替换默认配置',
+  'app.setting.production.hint':
+    '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
+};
diff --git a/react-ui/src/locales/zh-CN/settings.ts b/react-ui/src/locales/zh-CN/settings.ts
new file mode 100644
index 0000000..df8af43
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/settings.ts
@@ -0,0 +1,55 @@
+export default {
+  'app.settings.menuMap.basic': '基本设置',
+  'app.settings.menuMap.security': '安全设置',
+  'app.settings.menuMap.binding': '账号绑定',
+  'app.settings.menuMap.notification': '新消息通知',
+  'app.settings.basic.avatar': '头像',
+  'app.settings.basic.change-avatar': '更换头像',
+  'app.settings.basic.email': '邮箱',
+  'app.settings.basic.email-message': '请输入您的邮箱!',
+  'app.settings.basic.nickname': '昵称',
+  'app.settings.basic.nickname-message': '请输入您的昵称!',
+  'app.settings.basic.profile': '个人简介',
+  'app.settings.basic.profile-message': '请输入个人简介!',
+  'app.settings.basic.profile-placeholder': '个人简介',
+  'app.settings.basic.country': '国家/地区',
+  'app.settings.basic.country-message': '请输入您的国家或地区!',
+  'app.settings.basic.geographic': '所在省市',
+  'app.settings.basic.geographic-message': '请输入您的所在省市!',
+  'app.settings.basic.address': '街道地址',
+  'app.settings.basic.address-message': '请输入您的街道地址!',
+  'app.settings.basic.phone': '联系电话',
+  'app.settings.basic.phone-message': '请输入您的联系电话!',
+  'app.settings.basic.update': '更新基本信息',
+  'app.settings.security.strong': '强',
+  'app.settings.security.medium': '中',
+  'app.settings.security.weak': '弱',
+  'app.settings.security.password': '账户密码',
+  'app.settings.security.password-description': '当前密码强度',
+  'app.settings.security.phone': '密保手机',
+  'app.settings.security.phone-description': '已绑定手机',
+  'app.settings.security.question': '密保问题',
+  'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
+  'app.settings.security.email': '备用邮箱',
+  'app.settings.security.email-description': '已绑定邮箱',
+  'app.settings.security.mfa': 'MFA 设备',
+  'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
+  'app.settings.security.modify': '修改',
+  'app.settings.security.set': '设置',
+  'app.settings.security.bind': '绑定',
+  'app.settings.binding.taobao': '绑定淘宝',
+  'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
+  'app.settings.binding.alipay': '绑定支付宝',
+  'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
+  'app.settings.binding.dingding': '绑定钉钉',
+  'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
+  'app.settings.binding.bind': '绑定',
+  'app.settings.notification.password': '账户密码',
+  'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
+  'app.settings.notification.messages': '系统消息',
+  'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
+  'app.settings.notification.todo': '待办任务',
+  'app.settings.notification.todo-description': '待办任务将以站内信的形式通知',
+  'app.settings.open': '开',
+  'app.settings.close': '关',
+};
diff --git a/react-ui/src/locales/zh-CN/system/config.ts b/react-ui/src/locales/zh-CN/system/config.ts
new file mode 100644
index 0000000..5e1e764
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/config.ts
@@ -0,0 +1,14 @@
+export default {
+	'system.config.title': '参数配置',
+	'system.config.config_id': '参数主键',
+	'system.config.config_name': '参数名称',
+	'system.config.config_key': '参数键名',
+	'system.config.config_value': '参数键值',
+	'system.config.config_type': '系统内置',
+	'system.config.create_by': '创建者',
+	'system.config.create_time': '创建时间',
+	'system.config.update_by': '更新者',
+	'system.config.update_time': '更新时间',
+	'system.config.remark': '备注',
+	'system.config.refreshCache': '刷新缓存',
+};
diff --git a/react-ui/src/locales/zh-CN/system/dept.ts b/react-ui/src/locales/zh-CN/system/dept.ts
new file mode 100644
index 0000000..7774f2c
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/dept.ts
@@ -0,0 +1,18 @@
+export default {
+	'system.dept.title': '部门',
+	'system.dept.dept_id': '部门id',
+	'system.dept.parent_id': '父部门id',
+	'system.dept.parent_dept': '上级部门',
+	'system.dept.ancestors': '祖级列表',
+	'system.dept.dept_name': '部门名称',
+	'system.dept.order_num': '显示顺序',
+	'system.dept.leader': '负责人',
+	'system.dept.phone': '联系电话',
+	'system.dept.email': '邮箱',
+	'system.dept.status': '部门状态',
+	'system.dept.del_flag': '删除标志',
+	'system.dept.create_by': '创建者',
+	'system.dept.create_time': '创建时间',
+	'system.dept.update_by': '更新者',
+	'system.dept.update_time': '更新时间',
+};
diff --git a/react-ui/src/locales/zh-CN/system/dict-data.ts b/react-ui/src/locales/zh-CN/system/dict-data.ts
new file mode 100644
index 0000000..db2c742
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/dict-data.ts
@@ -0,0 +1,17 @@
+export default {
+  'system.dict.data.title': '字典数据',
+  'system.dict.data.dict_code': '字典编码',
+  'system.dict.data.dict_sort': '字典排序',
+  'system.dict.data.dict_label': '字典标签',
+  'system.dict.data.dict_value': '字典键值',
+  'system.dict.data.dict_type': '字典类型',
+  'system.dict.data.css_class': '样式属性',
+  'system.dict.data.list_class': '回显样式',
+  'system.dict.data.is_default': '是否默认',
+  'system.dict.data.status': '状态',
+  'system.dict.data.create_by': '创建者',
+  'system.dict.data.create_time': '创建时间',
+  'system.dict.data.update_by': '更新者',
+  'system.dict.data.update_time': '更新时间',
+  'system.dict.data.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/dict.ts b/react-ui/src/locales/zh-CN/system/dict.ts
new file mode 100644
index 0000000..cf00f66
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/dict.ts
@@ -0,0 +1,12 @@
+export default {
+  'system.dict.title': '字典类型',
+  'system.dict.dict_id': '字典主键',
+  'system.dict.dict_name': '字典名称',
+  'system.dict.dict_type': '字典类型',
+  'system.dict.status': '状态',
+  'system.dict.create_by': '创建者',
+  'system.dict.create_time': '创建时间',
+  'system.dict.update_by': '更新者',
+  'system.dict.update_time': '更新时间',
+  'system.dict.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/menu.ts b/react-ui/src/locales/zh-CN/system/menu.ts
new file mode 100644
index 0000000..8a01e58
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/menu.ts
@@ -0,0 +1,22 @@
+export default {
+	'system.menu.title': '菜单权限',
+	'system.menu.menu_id': '菜单编号',
+	'system.menu.menu_name': '菜单名称',
+	'system.menu.parent_id': '上级菜单',
+	'system.menu.order_num': '显示顺序',
+	'system.menu.path': '路由地址',
+	'system.menu.component': '组件路径',
+	'system.menu.query': '路由参数',
+	'system.menu.is_frame': '是否为外链',
+	'system.menu.is_cache': '是否缓存',
+	'system.menu.menu_type': '菜单类型',
+	'system.menu.visible': '显示状态',
+	'system.menu.status': '菜单状态',
+	'system.menu.perms': '权限标识',
+	'system.menu.icon': '菜单图标',
+	'system.menu.create_by': '创建者',
+	'system.menu.create_time': '创建时间',
+	'system.menu.update_by': '更新者',
+	'system.menu.update_time': '更新时间',
+	'system.menu.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/notice.ts b/react-ui/src/locales/zh-CN/system/notice.ts
new file mode 100644
index 0000000..ad55d51
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/notice.ts
@@ -0,0 +1,13 @@
+export default {
+	'system.notice.title': '通知公告',
+	'system.notice.notice_id': '公告编号',
+	'system.notice.notice_title': '公告标题',
+	'system.notice.notice_type': '公告类型',
+	'system.notice.notice_content': '公告内容',
+	'system.notice.status': '公告状态',
+	'system.notice.create_by': '创建者',
+	'system.notice.create_time': '创建时间',
+	'system.notice.update_by': '更新者',
+	'system.notice.update_time': '更新时间',
+	'system.notice.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/post.ts b/react-ui/src/locales/zh-CN/system/post.ts
new file mode 100644
index 0000000..40e589b
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/post.ts
@@ -0,0 +1,13 @@
+export default {
+	'system.post.title': '岗位信息',
+	'system.post.post_id': '岗位编号',
+	'system.post.post_code': '岗位编码',
+	'system.post.post_name': '岗位名称',
+	'system.post.post_sort': '显示顺序',
+	'system.post.status': '状态',
+	'system.post.create_by': '创建者',
+	'system.post.create_time': '创建时间',
+	'system.post.update_by': '更新者',
+	'system.post.update_time': '更新时间',
+	'system.post.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/role.ts b/react-ui/src/locales/zh-CN/system/role.ts
new file mode 100644
index 0000000..e88f979
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/role.ts
@@ -0,0 +1,21 @@
+export default {
+	'system.role.title': '角色信息',
+	'system.role.role_id': '角色编号',
+	'system.role.role_name': '角色名称',
+	'system.role.role_key': '权限字符',
+	'system.role.role_sort': '显示顺序',
+	'system.role.data_scope': '数据范围',
+	'system.role.menu_check_strictly': '菜单树选择项是否关联显示',
+	'system.role.dept_check_strictly': '部门树选择项是否关联显示',
+	'system.role.status': '角色状态',
+	'system.role.del_flag': '删除标志',
+	'system.role.create_by': '创建者',
+	'system.role.create_time': '创建时间',
+	'system.role.update_by': '更新者',
+	'system.role.update_time': '更新时间',
+	'system.role.remark': '备注',
+	'system.role.auth': '菜单权限',
+	'system.role.auth.user': '选择用户',
+	'system.role.auth.addUser': '添加用户',
+	'system.role.auth.cancelAll': '批量取消授权',
+};
diff --git a/react-ui/src/locales/zh-CN/system/user.ts b/react-ui/src/locales/zh-CN/system/user.ts
new file mode 100644
index 0000000..7d676d0
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/user.ts
@@ -0,0 +1,31 @@
+export default {
+	'system.user.title': '用户信息',
+	'system.user.user_id': '用户编号',
+	'system.user.dept_name': '部门',
+	'system.user.user_name': '用户账号',
+	'system.user.nick_name': '用户昵称',
+	'system.user.user_type': '用户类型',
+	'system.user.email': '用户邮箱',
+	'system.user.phonenumber': '手机号码',
+	'system.user.sex': '用户性别',
+	'system.user.avatar': '头像地址',
+	'system.user.password': '密码',
+	'system.user.status': '帐号状态',
+	'system.user.del_flag': '删除标志',
+	'system.user.login_ip': '最后登录IP',
+	'system.user.login_date': '最后登录时间',
+	'system.user.create_by': '创建者',
+	'system.user.create_time': '创建时间',
+	'system.user.update_by': '更新者',
+	'system.user.update_time': '更新时间',
+	'system.user.remark': '备注',
+	'system.user.post': '岗位',
+	'system.user.role': '角色',
+	'system.user.auth.role': '分配角色',
+	'system.user.reset.password': '密码重置',
+	'system.user.modify_info': '编辑用户信息',
+	'system.user.old_password': '旧密码',
+	'system.user.new_password': '新密码',
+	'system.user.confirm_password': '确认密码',
+	'system.user.modify_avatar': '修改头像',
+};
diff --git a/react-ui/src/locales/zh-TW.ts b/react-ui/src/locales/zh-TW.ts
new file mode 100644
index 0000000..6ad5f93
--- /dev/null
+++ b/react-ui/src/locales/zh-TW.ts
@@ -0,0 +1,20 @@
+import component from './zh-TW/component';
+import globalHeader from './zh-TW/globalHeader';
+import menu from './zh-TW/menu';
+import pwa from './zh-TW/pwa';
+import settingDrawer from './zh-TW/settingDrawer';
+import settings from './zh-TW/settings';
+
+export default {
+  'navBar.lang': '語言',
+  'layout.user.link.help': '幫助',
+  'layout.user.link.privacy': '隱私',
+  'layout.user.link.terms': '條款',
+  'app.preview.down.block': '下載此頁面到本地項目',
+  ...globalHeader,
+  ...menu,
+  ...settingDrawer,
+  ...settings,
+  ...pwa,
+  ...component,
+};
diff --git a/react-ui/src/locales/zh-TW/component.ts b/react-ui/src/locales/zh-TW/component.ts
new file mode 100644
index 0000000..ba48e29
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/component.ts
@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': '展開',
+  'component.tagSelect.collapse': '收起',
+  'component.tagSelect.all': '全部',
+};
diff --git a/react-ui/src/locales/zh-TW/globalHeader.ts b/react-ui/src/locales/zh-TW/globalHeader.ts
new file mode 100644
index 0000000..ed58451
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+  'component.globalHeader.search': '站內搜索',
+  'component.globalHeader.search.example1': '搜索提示壹',
+  'component.globalHeader.search.example2': '搜索提示二',
+  'component.globalHeader.search.example3': '搜索提示三',
+  'component.globalHeader.help': '使用手冊',
+  'component.globalHeader.notification': '通知',
+  'component.globalHeader.notification.empty': '妳已查看所有通知',
+  'component.globalHeader.message': '消息',
+  'component.globalHeader.message.empty': '您已讀完所有消息',
+  'component.globalHeader.event': '待辦',
+  'component.globalHeader.event.empty': '妳已完成所有待辦',
+  'component.noticeIcon.clear': '清空',
+  'component.noticeIcon.cleared': '清空了',
+  'component.noticeIcon.empty': '暫無資料',
+  'component.noticeIcon.view-more': '查看更多',
+};
diff --git a/react-ui/src/locales/zh-TW/menu.ts b/react-ui/src/locales/zh-TW/menu.ts
new file mode 100644
index 0000000..0ef54c9
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/menu.ts
@@ -0,0 +1,52 @@
+export default {
+  'menu.welcome': '歡迎',
+  'menu.more-blocks': '更多區塊',
+  'menu.home': '首頁',
+  'menu.admin': '权限',
+  'menu.admin.sub-page': '二级管理页',
+  'menu.login': '登錄',
+  'menu.register': '註冊',
+  'menu.register-result': '註冊結果',
+  'menu.dashboard': 'Dashboard',
+  'menu.dashboard.analysis': '分析頁',
+  'menu.dashboard.monitor': '監控頁',
+  'menu.dashboard.workplace': '工作臺',
+  'menu.exception.403': '403',
+  'menu.exception.404': '404',
+  'menu.exception.500': '500',
+  'menu.form': '表單頁',
+  'menu.form.basic-form': '基礎表單',
+  'menu.form.step-form': '分步表單',
+  'menu.form.step-form.info': '分步表單(填寫轉賬信息)',
+  'menu.form.step-form.confirm': '分步表單(確認轉賬信息)',
+  'menu.form.step-form.result': '分步表單(完成)',
+  'menu.form.advanced-form': '高級表單',
+  'menu.list': '列表頁',
+  'menu.list.table-list': '查詢表格',
+  'menu.list.basic-list': '標淮列表',
+  'menu.list.card-list': '卡片列表',
+  'menu.list.search-list': '搜索列表',
+  'menu.list.search-list.articles': '搜索列表(文章)',
+  'menu.list.search-list.projects': '搜索列表(項目)',
+  'menu.list.search-list.applications': '搜索列表(應用)',
+  'menu.profile': '詳情頁',
+  'menu.profile.basic': '基礎詳情頁',
+  'menu.profile.advanced': '高級詳情頁',
+  'menu.result': '結果頁',
+  'menu.result.success': '成功頁',
+  'menu.result.fail': '失敗頁',
+  'menu.exception': '异常页',
+  'menu.exception.not-permission': '403',
+  'menu.exception.not-find': '404',
+  'menu.exception.server-error': '500',
+  'menu.exception.trigger': '触发错误',
+  'menu.account': '個人頁',
+  'menu.account.center': '個人中心',
+  'menu.account.settings': '個人設置',
+  'menu.account.trigger': '觸發報錯',
+  'menu.account.logout': '退出登錄',
+  'menu.editor': '圖形編輯器',
+  'menu.editor.flow': '流程編輯器',
+  'menu.editor.mind': '腦圖編輯器',
+  'menu.editor.koni': '拓撲編輯器',
+};
diff --git a/react-ui/src/locales/zh-TW/pages.ts b/react-ui/src/locales/zh-TW/pages.ts
new file mode 100644
index 0000000..f9e265b
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/pages.ts
@@ -0,0 +1,68 @@
+export default {
+  'pages.layouts.userLayout.title': 'Ant Design 是西湖區最具影響力的 Web 設計規範',
+  'pages.login.accountLogin.tab': '賬戶密碼登錄',
+  'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼(admin/admin123)',
+  'pages.login.failure': '登錄失敗,請重試!',
+  'pages.login.success': '登錄成功!',
+  'pages.login.username.placeholder': '用戶名: admin',
+  'pages.login.username.required': '用戶名是必填項!',
+  'pages.login.password.placeholder': '密碼: admin123',
+  'pages.login.password.required': '密碼是必填項!',
+  'pages.login.phoneLogin.tab': '手機號登錄',
+  'pages.login.phoneLogin.errorMessage': '驗證碼錯誤',
+  'pages.login.phoneNumber.placeholder': '請輸入手機號!',
+  'pages.login.phoneNumber.required': '手機號是必填項!',
+  'pages.login.phoneNumber.invalid': '不合法的手機號!',
+  'pages.login.captcha.placeholder': '請輸入驗證碼!',
+  'pages.login.captcha.required': '驗證碼是必填項!',
+  'pages.login.phoneLogin.getVerificationCode': '獲取驗證碼',
+  'pages.getCaptchaSecondText': '秒後重新獲取',
+  'pages.login.rememberMe': '自動登錄',
+  'pages.login.forgotPassword': '忘記密碼 ?',
+  'pages.login.submit': '登錄',
+  'pages.login.loginWith': '其他登錄方式 :',
+  'pages.login.registerAccount': '註冊賬戶',
+  'pages.welcome.link': '歡迎使用',
+  'pages.welcome.alertMessage': '更快更強的重型組件,已經發布。',
+  'pages.admin.subPage.title': '這個頁面只有 admin 權限才能查看',
+  'pages.admin.subPage.alertMessage': 'umi ui 現已發佈,歡迎使用 npm run ui 啓動體驗。',
+  'pages.searchTable.createForm.newRule': '新建規則',
+  'pages.searchTable.updateForm.ruleConfig': '規則配置',
+  'pages.searchTable.updateForm.basicConfig': '基本信息',
+  'pages.searchTable.updateForm.ruleName.nameLabel': '規則名稱',
+  'pages.searchTable.updateForm.ruleName.nameRules': '請輸入規則名稱!',
+  'pages.searchTable.updateForm.ruleDesc.descLabel': '規則描述',
+  'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '請輸入至少五個字符',
+  'pages.searchTable.updateForm.ruleDesc.descRules': '請輸入至少五個字符的規則描述!',
+  'pages.searchTable.updateForm.ruleProps.title': '配置規則屬性',
+  'pages.searchTable.updateForm.object': '監控對象',
+  'pages.searchTable.updateForm.ruleProps.templateLabel': '規則模板',
+  'pages.searchTable.updateForm.ruleProps.typeLabel': '規則類型',
+  'pages.searchTable.updateForm.schedulingPeriod.title': '設定調度週期',
+  'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '開始時間',
+  'pages.searchTable.updateForm.schedulingPeriod.timeRules': '請選擇開始時間!',
+  'pages.searchTable.titleDesc': '描述',
+  'pages.searchTable.ruleName': '規則名稱爲必填項',
+  'pages.searchTable.titleCallNo': '服務調用次數',
+  'pages.searchTable.titleStatus': '狀態',
+  'pages.searchTable.nameStatus.default': '關閉',
+  'pages.searchTable.nameStatus.running': '運行中',
+  'pages.searchTable.nameStatus.online': '已上線',
+  'pages.searchTable.nameStatus.abnormal': '異常',
+  'pages.searchTable.titleUpdatedAt': '上次調度時間',
+  'pages.searchTable.exception': '請輸入異常原因!',
+  'pages.searchTable.titleOption': '操作',
+  'pages.searchTable.config': '配置',
+  'pages.searchTable.subscribeAlert': '訂閱警報',
+  'pages.searchTable.title': '查詢表格',
+  'pages.searchTable.new': '新建',
+  'pages.searchTable.edit': '編輯',
+  'pages.searchTable.delete': '刪除',
+  'pages.searchTable.export': '導出',
+  'pages.searchTable.chosen': '已選擇',
+  'pages.searchTable.item': '項',
+  'pages.searchTable.totalServiceCalls': '服務調用次數總計',
+  'pages.searchTable.tenThousand': '萬',
+  'pages.searchTable.batchDeletion': '批量刪除',
+  'pages.searchTable.batchApproval': '批量審批',
+};
diff --git a/react-ui/src/locales/zh-TW/pwa.ts b/react-ui/src/locales/zh-TW/pwa.ts
new file mode 100644
index 0000000..108a6e4
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': '當前處於離線狀態',
+  'app.pwa.serviceworker.updated': '有新內容',
+  'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
+  'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/react-ui/src/locales/zh-TW/settingDrawer.ts b/react-ui/src/locales/zh-TW/settingDrawer.ts
new file mode 100644
index 0000000..454da28
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+  'app.setting.pagestyle': '整體風格設置',
+  'app.setting.pagestyle.dark': '暗色菜單風格',
+  'app.setting.pagestyle.light': '亮色菜單風格',
+  'app.setting.content-width': '內容區域寬度',
+  'app.setting.content-width.fixed': '定寬',
+  'app.setting.content-width.fluid': '流式',
+  'app.setting.themecolor': '主題色',
+  'app.setting.themecolor.dust': '薄暮',
+  'app.setting.themecolor.volcano': '火山',
+  'app.setting.themecolor.sunset': '日暮',
+  'app.setting.themecolor.cyan': '明青',
+  'app.setting.themecolor.green': '極光綠',
+  'app.setting.themecolor.daybreak': '拂曉藍(默認)',
+  'app.setting.themecolor.geekblue': '極客藍',
+  'app.setting.themecolor.purple': '醬紫',
+  'app.setting.navigationmode': '導航模式',
+  'app.setting.sidemenu': '側邊菜單布局',
+  'app.setting.topmenu': '頂部菜單布局',
+  'app.setting.fixedheader': '固定 Header',
+  'app.setting.fixedsidebar': '固定側邊菜單',
+  'app.setting.fixedsidebar.hint': '側邊菜單布局時可配置',
+  'app.setting.hideheader': '下滑時隱藏 Header',
+  'app.setting.hideheader.hint': '固定 Header 時可配置',
+  'app.setting.othersettings': '其他設置',
+  'app.setting.weakmode': '色弱模式',
+  'app.setting.copy': '拷貝設置',
+  'app.setting.copyinfo': '拷貝成功,請到 config/defaultSettings.js 中替換默認配置',
+  'app.setting.production.hint':
+    '配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件',
+};
diff --git a/react-ui/src/locales/zh-TW/settings.ts b/react-ui/src/locales/zh-TW/settings.ts
new file mode 100644
index 0000000..dd45151
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/settings.ts
@@ -0,0 +1,55 @@
+export default {
+  'app.settings.menuMap.basic': '基本設置',
+  'app.settings.menuMap.security': '安全設置',
+  'app.settings.menuMap.binding': '賬號綁定',
+  'app.settings.menuMap.notification': '新消息通知',
+  'app.settings.basic.avatar': '頭像',
+  'app.settings.basic.change-avatar': '更換頭像',
+  'app.settings.basic.email': '郵箱',
+  'app.settings.basic.email-message': '請輸入您的郵箱!',
+  'app.settings.basic.nickname': '昵稱',
+  'app.settings.basic.nickname-message': '請輸入您的昵稱!',
+  'app.settings.basic.profile': '個人簡介',
+  'app.settings.basic.profile-message': '請輸入個人簡介!',
+  'app.settings.basic.profile-placeholder': '個人簡介',
+  'app.settings.basic.country': '國家/地區',
+  'app.settings.basic.country-message': '請輸入您的國家或地區!',
+  'app.settings.basic.geographic': '所在省市',
+  'app.settings.basic.geographic-message': '請輸入您的所在省市!',
+  'app.settings.basic.address': '街道地址',
+  'app.settings.basic.address-message': '請輸入您的街道地址!',
+  'app.settings.basic.phone': '聯系電話',
+  'app.settings.basic.phone-message': '請輸入您的聯系電話!',
+  'app.settings.basic.update': '更新基本信息',
+  'app.settings.security.strong': '強',
+  'app.settings.security.medium': '中',
+  'app.settings.security.weak': '弱',
+  'app.settings.security.password': '賬戶密碼',
+  'app.settings.security.password-description': '當前密碼強度',
+  'app.settings.security.phone': '密保手機',
+  'app.settings.security.phone-description': '已綁定手機',
+  'app.settings.security.question': '密保問題',
+  'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
+  'app.settings.security.email': '備用郵箱',
+  'app.settings.security.email-description': '已綁定郵箱',
+  'app.settings.security.mfa': 'MFA 設備',
+  'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
+  'app.settings.security.modify': '修改',
+  'app.settings.security.set': '設置',
+  'app.settings.security.bind': '綁定',
+  'app.settings.binding.taobao': '綁定淘寶',
+  'app.settings.binding.taobao-description': '當前未綁定淘寶賬號',
+  'app.settings.binding.alipay': '綁定支付寶',
+  'app.settings.binding.alipay-description': '當前未綁定支付寶賬號',
+  'app.settings.binding.dingding': '綁定釘釘',
+  'app.settings.binding.dingding-description': '當前未綁定釘釘賬號',
+  'app.settings.binding.bind': '綁定',
+  'app.settings.notification.password': '賬戶密碼',
+  'app.settings.notification.password-description': '其他用戶的消息將以站內信的形式通知',
+  'app.settings.notification.messages': '系統消息',
+  'app.settings.notification.messages-description': '系統消息將以站內信的形式通知',
+  'app.settings.notification.todo': '待辦任務',
+  'app.settings.notification.todo-description': '待辦任務將以站內信的形式通知',
+  'app.settings.open': '開',
+  'app.settings.close': '關',
+};
diff --git a/react-ui/src/manifest.json b/react-ui/src/manifest.json
new file mode 100644
index 0000000..839bc5b
--- /dev/null
+++ b/react-ui/src/manifest.json
@@ -0,0 +1,22 @@
+{
+  "name": "Ant Design Pro",
+  "short_name": "Ant Design Pro",
+  "display": "standalone",
+  "start_url": "./?utm_source=homescreen",
+  "theme_color": "#002140",
+  "background_color": "#001529",
+  "icons": [
+    {
+      "src": "icons/icon-192x192.png",
+      "sizes": "192x192"
+    },
+    {
+      "src": "icons/icon-128x128.png",
+      "sizes": "128x128"
+    },
+    {
+      "src": "icons/icon-512x512.png",
+      "sizes": "512x512"
+    }
+  ]
+}
diff --git a/react-ui/src/pages/404.tsx b/react-ui/src/pages/404.tsx
new file mode 100644
index 0000000..0263687
--- /dev/null
+++ b/react-ui/src/pages/404.tsx
@@ -0,0 +1,18 @@
+import { history } from '@umijs/max';
+import { Button, Result } from 'antd';
+import React from 'react';
+
+const NoFoundPage: React.FC = () => (
+  <Result
+    status="404"
+    title="404"
+    subTitle="Sorry, the page you visited does not exist."
+    extra={
+      <Button type="primary" onClick={() => history.push('/')}>
+        Back Home
+      </Button>
+    }
+  />
+);
+
+export default NoFoundPage;
diff --git a/react-ui/src/pages/Monitor/Cache/List.tsx b/react-ui/src/pages/Monitor/Cache/List.tsx
new file mode 100644
index 0000000..af6147a
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Cache/List.tsx
@@ -0,0 +1,240 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { clearCacheAll, clearCacheKey, clearCacheName, getCacheValue, listCacheKey, listCacheName } from '@/services/monitor/cachelist';
+import { Button, Card, Col, Form, FormInstance, Input, message, Row, Table } from 'antd';
+import styles from './index.less';
+import { FormattedMessage } from '@umijs/max';
+import { ReloadOutlined } from '@ant-design/icons';
+import { ProForm } from '@ant-design/pro-components';
+
+const { TextArea } = Input;
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2022/06/27
+ *
+ * */
+
+
+
+const CacheList: React.FC = () => {
+  const [cacheNames, setCacheNames] = useState<any>([]);
+  const [currentCacheName, setCurrentCacheName] = useState<any>([]);
+  const [cacheKeys, setCacheKeys] = useState<any>([]);
+  const [form] = Form.useForm();
+
+  const getCacheNames = () => {
+    listCacheName().then((res) => {
+      if (res.code === 200) {
+        setCacheNames(res.data);
+      }
+    });
+  }
+
+  useEffect(() => {
+    getCacheNames();
+  }, []);
+
+  const getCacheKeys = (cacheName: string) => {
+    listCacheKey(cacheName).then(res => {
+      if (res.code === 200) {
+        let index = 0;
+        const keysData = res.data.map((item: any) => {
+          return {
+            index: index++,
+            cacheKey: item
+          }
+        })
+        setCacheKeys(keysData);
+      }
+    });
+  };
+
+  const onClearAll = async () => {
+    clearCacheAll().then(res => {
+      if(res.code === 200) {
+        message.success("清理全部缓存成功");
+      }
+    });
+  };
+
+  const onClearAllFailed = (errorInfo: any) => {
+    message.error('Failed:', errorInfo);
+  };
+
+  const refreshCacheNames = () => {
+    getCacheNames();
+    message.success("刷新缓存列表成功");
+  };
+
+  const refreshCacheKeys = () => {
+    getCacheKeys(currentCacheName);
+    message.success("刷新键名列表成功");
+  };
+
+  const columns = [
+    {
+      title: '缓存名称',
+      dataIndex: 'cacheName',
+      key: 'cacheName',
+      render: (_: any, record: any) => {
+        return record.cacheName.replace(":", "");
+      }
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      key: 'remark',
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '40px',
+      valueType: 'option',
+      render: (_: any, record: API.Monitor.CacheContent) => [
+        <Button
+          type="link"
+          size="small"
+          key="remove"
+          danger
+          onClick={() => {
+            clearCacheName(record.cacheName).then(res => {
+              if(res.code === 200) {
+                message.success("清理缓存名称[" + record.cacheName + "]成功");
+                getCacheKeys(currentCacheName);
+              }
+            });
+          }}
+        >
+          <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+        </Button>,
+      ]
+    }
+  ];
+
+  const cacheKeysColumns = [
+    {
+      title: '序号',
+      dataIndex: 'index',
+      key: 'index'
+    },
+    {
+      title: '缓存键名',
+      dataIndex: 'cacheKey',
+      key: 'cacheKey',
+      render: (_: any, record: any) => {
+        return record.cacheKey.replace(currentCacheName, "");
+      }
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '40px',
+      valueType: 'option',
+      render: (_: any, record: API.Monitor.CacheContent) => [
+        <Button
+          type="link"
+          size="small"
+          key="remove"
+          danger
+          onClick={() => {
+            console.log(record)
+            clearCacheKey(record.cacheKey).then(res => {
+              if(res.code === 200) {
+                message.success("清理缓存键名[" + record.cacheKey + "]成功");
+                getCacheKeys(currentCacheName);
+              }
+            });
+          }}
+        >
+          <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+        </Button>,
+      ]
+    }
+  ];
+
+  return (
+    <div>
+      <Row gutter={[24, 24]}>
+        <Col span={8}>
+          <Card title="缓存列表" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ refreshCacheNames()}} type="link" />} className={styles.card}>
+            <Table
+              rowKey="cacheName"
+              dataSource={cacheNames}
+              columns={columns}
+              onRow={(record: API.Monitor.CacheContent) => {
+                return {
+                  onClick: () => {
+                    setCurrentCacheName(record.cacheName);
+                    getCacheKeys(record.cacheName);
+                  },
+                };
+              }}
+            />
+          </Card>
+        </Col>
+        <Col span={8}>
+          <Card title="键名列表" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ refreshCacheKeys()}} type="link" />} className={styles.card}>
+            <Table
+              rowKey="index"
+              dataSource={cacheKeys}
+              columns={cacheKeysColumns}
+              onRow={(record: any) => {
+                return {
+                  onClick: () => {
+                    getCacheValue(currentCacheName, record.cacheKey).then(res => {
+                      if (res.code === 200) {
+                        form.resetFields();
+                        form.setFieldsValue({
+                          cacheName: res.data.cacheName,
+                          cacheKey: res.data.cacheKey,
+                          cacheValue: res.data.cacheValue,
+                          remark: res.data.remark,
+                        });
+                      }
+                    });
+                  },
+                };
+              }}
+            />
+          </Card>
+        </Col>
+        <Col span={8}>
+          <Card title="缓存内容" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ onClearAll()}} type="link" >清理全部</Button>} className={styles.card}>
+            <ProForm
+              name="basic"
+              form={form}
+              labelCol={{ span: 8 }}
+              wrapperCol={{ span: 16 }}
+              onFinish={onClearAll}
+              onFinishFailed={onClearAllFailed}
+              autoComplete="off"
+            >
+              <Form.Item
+                label="缓存名称"
+                name="cacheName"
+              >
+                <Input />
+              </Form.Item>
+              <Form.Item
+                label="缓存键名"
+                name="cacheKey"
+              >
+                <Input />
+              </Form.Item>
+              <Form.Item
+                label="缓存内容"
+                name="cacheValue"
+              >
+                <TextArea autoSize={{ minRows: 2 }} />
+              </Form.Item>
+            </ProForm>
+          </Card>
+        </Col>
+      </Row>
+    </div>
+  );
+};
+
+export default CacheList;
diff --git a/react-ui/src/pages/Monitor/Cache/List/index.less b/react-ui/src/pages/Monitor/Cache/List/index.less
new file mode 100644
index 0000000..0c5aace
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Cache/List/index.less
@@ -0,0 +1,10 @@
+
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ *
+ * */
+
diff --git a/react-ui/src/pages/Monitor/Cache/index.less b/react-ui/src/pages/Monitor/Cache/index.less
new file mode 100644
index 0000000..7112c50
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Cache/index.less
@@ -0,0 +1,33 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+ 
+.card {
+  margin-bottom: 12px;
+}
+
+
+.miniChart {
+  position: relative;
+  width: 100%;
+  .chartContent {
+    position: absolute;
+    bottom: -28px;
+    width: 100%;
+    > div {
+      margin: 0 -5px;
+      overflow: hidden;
+    }
+  }
+  .chartLoading {
+    position: absolute;
+    top: 16px;
+    left: 50%;
+    margin-left: -7px;
+  }
+}
diff --git a/react-ui/src/pages/Monitor/Cache/index.tsx b/react-ui/src/pages/Monitor/Cache/index.tsx
new file mode 100644
index 0000000..4c247ee
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Cache/index.tsx
@@ -0,0 +1,201 @@
+import React, { useEffect, useState } from 'react';
+import { Card, Col, Row, Table } from 'antd';
+import { DataItem } from '@antv/g2plot/esm/interface/config';
+import { Gauge, Pie } from '@ant-design/plots';
+import styles from './index.less';
+import { getCacheInfo } from '@/services/monitor/cache';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ *
+ * */
+
+
+const columns = [
+  {
+    title: 'col1',
+    dataIndex: 'col1',
+    key: 'col1',
+  },
+  {
+    title: 'col2',
+    dataIndex: 'col2',
+    key: 'col2',
+  },
+  {
+    title: 'col3',
+    dataIndex: 'col3',
+    key: 'col3',
+  },
+  {
+    title: 'col4',
+    dataIndex: 'col4',
+    key: 'col4',
+  },
+  {
+    title: 'col5',
+    dataIndex: 'col5',
+    key: 'col5',
+  },
+  {
+    title: 'col6',
+    dataIndex: 'col6',
+    key: 'col6',
+  },
+  {
+    title: 'col7',
+    dataIndex: 'col7',
+    key: 'col7',
+  },
+  {
+    title: 'col8',
+    dataIndex: 'col8',
+    key: 'col8',
+  },
+];
+
+const usageFormatter = (val: string): string => {
+  switch (val) {
+    case '10':
+      return '100%';
+    case '8':
+      return '80%';
+    case '6':
+      return '60%';
+    case '4':
+      return '40%';
+    case '2':
+      return '20%';
+    case '0':
+      return '0%';
+    default:
+      return '';
+  }
+};
+
+const CacheInfo: React.FC = () => {
+  const [baseInfoData, setBaseInfoData] = useState<any>([]);
+  const [memUsage, setMemUsage] = useState<Number>(0);
+  const [memUsageTitle, setMemUsageTitle] = useState<any>([]);
+  const [cmdInfoData, setCmdInfoData] = useState<DataItem[]>([]);
+  useEffect(() => {
+    getCacheInfo().then((res) => {
+      if (res.code === 200) {
+        const baseinfo = [];
+        baseinfo.push({
+          col1: 'Redis版本',
+          col2: res.data.info.redis_version,
+          col3: '运行模式',
+          col4: res.data.info.redis_mode === 'standalone' ? '单机' : '集群',
+          col5: '端口',
+          col6: res.data.info.tcp_port,
+          col7: '客户端数',
+          col8: res.data.info.connected_clients,
+        });
+        baseinfo.push({
+          col1: '运行时间(天)',
+          col2: res.data.info.uptime_in_days,
+          col3: '使用内存',
+          col4: res.data.info.used_memory_human,
+          col5: '使用CPU',
+          col6: `${res.data.info.used_cpu_user_children}%`,
+          col7: '内存配置',
+          col8: res.data.info.maxmemory_human,
+        });
+        baseinfo.push({
+          col1: 'AOF是否开启',
+          col2: res.data.info.aof_enabled === '0' ? '否' : '是',
+          col3: 'RDB是否成功',
+          col4: res.data.info.rdb_last_bgsave_status,
+          col5: 'Key数量',
+          col6: res.data.dbSize,
+          col7: '网络入口/出口',
+          col8: `${res.data.info.instantaneous_input_kbps}/${res.data.info.instantaneous_output_kbps}kps`,
+        });
+        setBaseInfoData(baseinfo);
+
+        const data: VisitDataType[] = res.data.commandStats.map((item) => {
+          return {
+            x: item.name,
+            y: Number(item.value),
+          };
+        });
+
+        setCmdInfoData(data);
+        setMemUsageTitle(res.data.info.used_memory_human);
+        setMemUsage(res.data.info.used_memory / res.data.info.total_system_memory);
+      }
+    });
+  }, []);
+
+  return (
+    <div>
+      <Row gutter={[24, 24]}>
+        <Col span={24}>
+          <Card title="基本信息" className={styles.card}>
+            <Table
+              rowKey="col1"
+              pagination={false}
+              showHeader={false}
+              dataSource={baseInfoData}
+              columns={columns}
+            />
+          </Card>
+        </Col>
+      </Row>
+      <Row gutter={[24, 24]}>
+        <Col span={12}>
+          <Card title="命令统计" className={styles.card}>
+            <Pie
+              height={320}
+              radius={0.8}
+              innerRadius={0.5}
+              angleField="y"
+              colorField="x"
+              data={cmdInfoData as any}
+              legend={false}
+              label={{
+                position: 'spider',
+                text: (item: { x: number; y: number }) => {
+                  return `${item.x}: ${item.y}`;
+                },
+              }}
+            />
+          </Card>
+        </Col>
+        <Col span={12}>
+          <Card title="内存信息" className={styles.card}>
+            <Gauge
+              title={memUsageTitle}
+              height={320}
+              percent={memUsage}
+              formatter={usageFormatter}
+              padding={-16}
+              style={{
+                textContent: () => { return memUsage.toFixed(2).toString() + '%'},
+              }}
+              data={
+                {
+                  target: memUsage,
+                  total: 100,
+                  name: 'score',
+                  thresholds: [20, 40, 60, 80, 100],
+                } as any
+              }
+              meta={{
+                color: {
+                  range: ['#C3F71F', '#B5E61D', '#FFC90E', '#FF7F27', '#FF2518'],
+                },
+              }}
+            />
+          </Card>
+        </Col>
+      </Row>
+    </div>
+  );
+};
+
+export default CacheInfo;
diff --git a/react-ui/src/pages/Monitor/Druid/index.tsx b/react-ui/src/pages/Monitor/Druid/index.tsx
new file mode 100644
index 0000000..55692d2
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Druid/index.tsx
@@ -0,0 +1,31 @@
+import React, { useEffect } from 'react'; 
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+const DruidInfo: React.FC = () => {
+  useEffect(() => {
+    const frame = document.getElementById('bdIframe');
+    if (frame) {
+      const deviceWidth = document.documentElement.clientWidth;
+      const deviceHeight = document.documentElement.clientHeight;
+      frame.style.width = `${Number(deviceWidth) - 220}px`;
+      frame.style.height = `${Number(deviceHeight) - 120}px`;
+    }
+  });
+
+  return (
+    <iframe
+      style={{ width: '100%', border: '0px', height: '100%' }}
+      src={`/api/druid/login.html`}
+      id="bdIframe"
+     />
+    // </WrapContent>
+  );
+};
+
+export default DruidInfo;
diff --git a/react-ui/src/pages/Monitor/Job/detail.tsx b/react-ui/src/pages/Monitor/Job/detail.tsx
new file mode 100644
index 0000000..e27bfb7
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Job/detail.tsx
@@ -0,0 +1,131 @@
+import React, { useEffect } from 'react';
+import { Modal, Descriptions, Button } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { getValueEnumLabel } from '@/utils/options';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+export type OperlogFormValueType = Record<string, unknown> & Partial<API.Monitor.Job>;
+
+export type OperlogFormProps = {
+  onCancel: (flag?: boolean, formVals?: OperlogFormValueType) => void;
+  open: boolean;
+  values: Partial<API.Monitor.Job>;
+  statusOptions: DictValueEnumObj;
+};
+
+const OperlogForm: React.FC<OperlogFormProps> = (props) => {
+  const { values, statusOptions } = props;
+
+  useEffect(() => {}, [props]);
+
+  const intl = useIntl();
+
+  const misfirePolicy: any = {
+    '0': '默认策略',
+    '1': '立即执行',
+    '2': '执行一次',
+    '3': '放弃执行',
+  };
+
+  const handleCancel = () => {
+    props.onCancel();
+  };
+
+  return (
+    <Modal
+      width={800}
+      title={intl.formatMessage({
+        id: 'monitor.job.detail',
+        defaultMessage: '操作日志详细信息',
+      })}
+      open={props.open}
+      destroyOnClose
+      onCancel={handleCancel}
+      footer={[
+        <Button key="back" onClick={handleCancel}>
+          关闭
+        </Button>,
+      ]}
+    >
+      <Descriptions column={24}>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />}
+        >
+          {values.jobId}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />}
+        >
+          {values.jobName}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />}
+        >
+          {values.jobGroup}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.concurrent" defaultMessage="是否并发执行" />}
+        >
+          {values.concurrent === '1' ? '禁止' : '允许'}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={
+            <FormattedMessage id="monitor.job.misfire_policy" defaultMessage="计划执行错误策略" />
+          }
+        >
+          {misfirePolicy[values.misfirePolicy ? values.misfirePolicy : '0']}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.create_time" defaultMessage="创建时间" />}
+        >
+          {values.createTime?.toString()}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.status" defaultMessage="状态" />}
+        >
+          {getValueEnumLabel(statusOptions, values.status, '未知')}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={
+            <FormattedMessage id="monitor.job.next_valid_time" defaultMessage="下次执行时间" />
+          }
+        >
+          {values.nextValidTime}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={
+            <FormattedMessage id="monitor.job.cron_expression" defaultMessage="cron执行表达式" />
+          }
+        >
+          {values.cronExpression}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={
+            <FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标字符串" />
+          }
+        >
+          {values.invokeTarget}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default OperlogForm;
diff --git a/react-ui/src/pages/Monitor/Job/edit.tsx b/react-ui/src/pages/Monitor/Job/edit.tsx
new file mode 100644
index 0000000..e4bfc74
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Job/edit.tsx
@@ -0,0 +1,232 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormTextArea,
+  ProFormRadio,
+  ProFormSelect,
+  ProFormCaptcha,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictOptionType, DictValueEnumObj } from '@/components/DictTag';
+
+/**
+ * 定时任务调度 Edit Form
+ * 
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+export type JobFormData = Record<string, unknown> & Partial<API.Monitor.Job>;
+
+export type JobFormProps = {
+  onCancel: (flag?: boolean, formVals?: JobFormData) => void;
+  onSubmit: (values: JobFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Job>;
+  jobGroupOptions: DictOptionType[];
+  statusOptions: DictValueEnumObj;
+};
+
+const JobForm: React.FC<JobFormProps> = (props) => {
+  const [form] = Form.useForm();
+  const { jobGroupOptions, statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      jobId: props.values.jobId,
+      jobName: props.values.jobName,
+      jobGroup: props.values.jobGroup,
+      invokeTarget: props.values.invokeTarget,
+      cronExpression: props.values.cronExpression,
+      misfirePolicy: props.values.misfirePolicy,
+      concurrent: props.values.concurrent,
+      status: props.values.status,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+    form.resetFields();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as JobFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'monitor.job.title',
+        defaultMessage: '编辑定时任务调度',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="jobId"
+          label={intl.formatMessage({
+            id: 'monitor.job.job_id',
+            defaultMessage: '任务编号',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入任务编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入任务编号!" defaultMessage="请输入任务编号!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="jobName"
+          label={intl.formatMessage({
+            id: 'monitor.job.job_name',
+            defaultMessage: '任务名称',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入任务名称"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入任务名称!" defaultMessage="请输入任务名称!" />,
+            },
+          ]}
+        />
+        <ProFormSelect
+          name="jobGroup"
+          options={jobGroupOptions}
+          label={intl.formatMessage({
+            id: 'monitor.job.job_group',
+            defaultMessage: '任务组名',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入任务组名"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入任务组名!" defaultMessage="请输入任务组名!" />,
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="invokeTarget"
+          label={intl.formatMessage({
+            id: 'monitor.job.invoke_target',
+            defaultMessage: '调用目标字符串',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入调用目标字符串"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入调用目标字符串!" defaultMessage="请输入调用目标字符串!" />,
+            },
+          ]}
+        />
+        <ProFormCaptcha
+          name="cronExpression"
+          label={intl.formatMessage({
+            id: 'monitor.job.cron_expression',
+            defaultMessage: 'cron执行表达式',
+          })}
+          captchaTextRender={() => "生成表达式"}
+          onGetCaptcha={() => {
+            // form.setFieldValue('cronExpression', '0/20 * * * * ?');
+            return new Promise((resolve, reject) => {
+              reject();
+            });
+          }}
+        />
+        <ProFormRadio.Group
+          name="misfirePolicy"
+          label={intl.formatMessage({
+            id: 'monitor.job.misfire_policy',
+            defaultMessage: '计划执行错误策略',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入计划执行错误策略"
+          valueEnum={{
+            0: '立即执行',
+            1: '执行一次',
+            3: '放弃执行'
+          }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入计划执行错误策略!" defaultMessage="请输入计划执行错误策略!" />,
+            },
+          ]}
+          fieldProps={{
+            optionType: "button",
+            buttonStyle: "solid"
+          }}
+        />
+        <ProFormRadio.Group
+          name="concurrent"
+          label={intl.formatMessage({
+            id: 'monitor.job.concurrent',
+            defaultMessage: '是否并发执行',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入是否并发执行"
+          valueEnum={{
+            0: '允许',
+            1: '禁止',
+          }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入是否并发执行!" defaultMessage="请输入是否并发执行!" />,
+            },
+          ]}
+          fieldProps={{
+            optionType: "button",
+            buttonStyle: "solid"
+          }}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'monitor.job.status',
+            defaultMessage: '状态',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default JobForm;
diff --git a/react-ui/src/pages/Monitor/Job/index.tsx b/react-ui/src/pages/Monitor/Job/index.tsx
new file mode 100644
index 0000000..43030d9
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Job/index.tsx
@@ -0,0 +1,460 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import { Dropdown, FormInstance, Space } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined } from '@ant-design/icons';
+import { getJobList, removeJob, addJob, updateJob, exportJob, runJob } from '@/services/monitor/job';
+import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
+import UpdateForm from './edit';
+import DetailForm from './detail';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 定时任务调度 List Page
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Job) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addJob({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Job) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateJob(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Job[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeJob(selectedRows.map((row) => row.jobId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.Monitor.Job) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.jobId];
+    const resp = await removeJob(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportJob();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const JobTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+  const [detailModalVisible, setDetailModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.Monitor.Job>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Job[]>([]);
+
+  const [jobGroupOptions, setJobGroupOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictSelectOption('sys_job_group').then((data) => {
+      setJobGroupOptions(data);
+    });
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Job>[] = [
+    {
+      title: <FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />,
+      dataIndex: 'jobId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />,
+      dataIndex: 'jobName',
+      valueType: 'text',
+      render: (dom, record) => {
+        return (
+          <a
+            onClick={() => {
+              setDetailModalVisible(true);
+              setCurrentRow(record);
+            }}
+          >
+            {dom}
+          </a>
+        );
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />,
+      dataIndex: 'jobGroup',
+      valueType: 'text',
+      valueEnum: jobGroupOptions,
+      render: (_, record) => {
+        return (<DictTag options={jobGroupOptions} value={record.jobGroup} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标字符串" />,
+      dataIndex: 'invokeTarget',
+      valueType: 'textarea',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.cron_expression" defaultMessage="cron执行表达式" />,
+      dataIndex: 'cronExpression',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.status" defaultMessage="状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          icon = <EditOutlined />
+          hidden={!access.hasPerms('monitor:job:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          icon = <DeleteOutlined />
+          hidden={!access.hasPerms('monitor:job:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+        <Dropdown
+          key="more"
+          menu={{
+            items: [
+              {
+                label: '执行一次',
+                key: 'runOnce',
+              },
+              {
+                label: '详细',
+                key: 'detail',
+              },
+              {
+                label: '历史',
+                key: 'log',
+              },
+            ],
+            onClick: ({ key }) => {
+              if (key === 'runOnce') {
+                Modal.confirm({
+                  title: '警告',
+                  content: '确认要立即执行一次?',
+                  okText: '确认',
+                  cancelText: '取消',
+                  onOk: async () => {
+                    const success = await runJob(record.jobId, record.jobGroup);
+                    if (success) {
+                      message.success('执行成功');
+                    }
+                  },
+                });
+              }
+              else if (key === 'detail') {
+                setDetailModalVisible(true);
+                setCurrentRow(record);
+              }
+              else if( key === 'log') {
+                history.push(`/monitor/job-log/index/${record.jobId}`);
+              }
+            }
+          }}
+        >
+          <a className="ant-dropdown-link" onClick={(e) => e.preventDefault()}>
+            <Space>
+              <DownOutlined />
+              更多
+            </Space>
+          </a>
+        </Dropdown>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Job>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="jobId"
+          key="jobList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('monitor:job:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:job:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('monitor:job:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getJobList({ ...params } as API.Monitor.JobListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('monitor:job:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.jobId) {
+            success = await handleUpdate({ ...values } as API.Monitor.Job);
+          } else {
+            success = await handleAdd({ ...values } as API.Monitor.Job);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        jobGroupOptions={jobGroupOptions||{}}
+        statusOptions={statusOptions}
+      />
+      <DetailForm
+        onCancel={() => {
+          setDetailModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={detailModalVisible}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default JobTableList;
diff --git a/react-ui/src/pages/Monitor/JobLog/detail.tsx b/react-ui/src/pages/Monitor/JobLog/detail.tsx
new file mode 100644
index 0000000..560889c
--- /dev/null
+++ b/react-ui/src/pages/Monitor/JobLog/detail.tsx
@@ -0,0 +1,105 @@
+import { getValueEnumLabel } from '@/utils/options';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { Descriptions, Modal } from 'antd';
+import React, { useEffect } from 'react';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+export type JobLogFormValueType = Record<string, unknown> & Partial<API.Monitor.JobLog>;
+
+export type JobLogFormProps = {
+  onCancel: (flag?: boolean, formVals?: JobLogFormValueType) => void;
+  open: boolean;
+  values: Partial<API.Monitor.JobLog>;
+  statusOptions: DictValueEnumObj;
+  jobGroupOptions: DictValueEnumObj;
+};
+
+const JobLogDetailForm: React.FC<JobLogFormProps> = (props) => {
+
+  const { values, statusOptions, jobGroupOptions } = props;
+
+  useEffect(() => {
+  }, []);
+
+  const intl = useIntl();
+  const handleOk = () => {
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'monitor.job.log.title',
+        defaultMessage: '定时任务调度日志',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Descriptions column={24}>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />}
+        >
+          {values.jobLogId}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.create_time" defaultMessage="执行时间" />}
+        >
+          {values.createTime?.toString()}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />}
+        >
+          {values.jobName}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />}
+        >
+          {getValueEnumLabel(jobGroupOptions, values.jobGroup, '无')}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标" />}
+        >
+          {values.invokeTarget}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.job.log.job_message" defaultMessage="日志信息" />}
+        >
+          {values.jobMessage}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.job.log.exception_info" defaultMessage="异常信息" />}
+        >
+          {values.exceptionInfo}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.status" defaultMessage="执行状态" />}
+        >
+          {getValueEnumLabel(statusOptions, values.status, '未知')}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default JobLogDetailForm;
diff --git a/react-ui/src/pages/Monitor/JobLog/index.tsx b/react-ui/src/pages/Monitor/JobLog/index.tsx
new file mode 100644
index 0000000..46e3313
--- /dev/null
+++ b/react-ui/src/pages/Monitor/JobLog/index.tsx
@@ -0,0 +1,343 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, useParams, history } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getJobLogList, removeJobLog, exportJobLog } from '@/services/monitor/jobLog';
+import DetailForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import { getJob } from '@/services/monitor/job';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 定时任务调度日志 List Page
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.JobLog[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeJobLog(selectedRows.map((row) => row.jobLogId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.Monitor.JobLog) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.jobLogId];
+    const resp = await removeJobLog(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 清空日志数据
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportJobLog();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const JobLogTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalOpen, setModalOpen] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.Monitor.JobLog>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.JobLog[]>([]);
+
+  const [jobGroupOptions, setJobGroupOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const [queryParams, setQueryParams] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  const params = useParams();
+  if (params.id === undefined) {
+    history.push('/monitor/job');
+  }
+  const jobId = params.id || 0;
+  useEffect(() => {
+    if (jobId !== undefined && jobId !== 0) {
+      getJob(Number(jobId)).then(response => {
+        setQueryParams({
+          jobName: response.data.jobName,
+          jobGroup: response.data.jobGroup
+        });
+      });
+    }
+    getDictValueEnum('sys_job_status').then((data) => {
+      setStatusOptions(data);
+    });
+    getDictValueEnum('sys_job_group').then((data) => {
+      setJobGroupOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.JobLog>[] = [
+    {
+      title: <FormattedMessage id="monitor.job.log.job_log_id" defaultMessage="任务日志编号" />,
+      dataIndex: 'jobLogId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.job_name" defaultMessage="任务名称" />,
+      dataIndex: 'jobName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.job_group" defaultMessage="任务组名" />,
+      dataIndex: 'jobGroup',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.invoke_target" defaultMessage="调用目标字符串" />,
+      dataIndex: 'invokeTarget',
+      valueType: 'textarea',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.job_message" defaultMessage="日志信息" />,
+      dataIndex: 'jobMessage',
+      valueType: 'textarea',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.status" defaultMessage="执行状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.create_time" defaultMessage="异常信息" />,
+      dataIndex: 'createTime',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('monitor:job-log:edit')}
+          onClick={() => {
+            setModalOpen(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('monitor:job-log:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.JobLog>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="jobLogId"
+          key="job-logList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('monitor:job-log:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalOpen(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:job-log:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('monitor:job-log:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          params={queryParams}
+          request={(params) =>
+            getJobLogList({ ...params } as API.Monitor.JobLogListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('monitor:job-log:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <DetailForm
+        onCancel={() => {
+          setModalOpen(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalOpen}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+        jobGroupOptions={jobGroupOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default JobLogTableList;
diff --git a/react-ui/src/pages/Monitor/Logininfor/edit.tsx b/react-ui/src/pages/Monitor/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Logininfor/edit.tsx
@@ -0,0 +1,216 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTimePicker,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type LogininforFormData = Record<string, unknown> & Partial<API.Monitor.Logininfor>;
+
+export type LogininforFormProps = {
+  onCancel: (flag?: boolean, formVals?: LogininforFormData) => void;
+  onSubmit: (values: LogininforFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Logininfor>;
+  statusOptions: DictValueEnumObj;
+};
+
+const LogininforForm: React.FC<LogininforFormProps> = (props) => {
+  const [form] = Form.useForm();
+  
+  const { statusOptions, } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			infoId: props.values.infoId,
+			userName: props.values.userName,
+			ipaddr: props.values.ipaddr,
+			loginLocation: props.values.loginLocation,
+			browser: props.values.browser,
+			os: props.values.os,
+			status: props.values.status,
+			msg: props.values.msg,
+			loginTime: props.values.loginTime,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+    form.resetFields();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as LogininforFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.logininfor.title',
+        defaultMessage: '编辑系统访问记录',
+      })}
+      open={props.open}
+      destroyOnClose
+      forceRender
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="infoId"
+          label={intl.formatMessage({
+            id: 'system.logininfor.info_id',
+            defaultMessage: '访问编号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入访问编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入访问编号!" defaultMessage="请输入访问编号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="userName"
+          label={intl.formatMessage({
+            id: 'system.logininfor.user_name',
+            defaultMessage: '用户账号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入用户账号"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入用户账号!" defaultMessage="请输入用户账号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="ipaddr"
+          label={intl.formatMessage({
+            id: 'system.logininfor.ipaddr',
+            defaultMessage: '登录IP地址',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录IP地址"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录IP地址!" defaultMessage="请输入登录IP地址!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="loginLocation"
+          label={intl.formatMessage({
+            id: 'system.logininfor.login_location',
+            defaultMessage: '登录地点',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录地点"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录地点!" defaultMessage="请输入登录地点!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="browser"
+          label={intl.formatMessage({
+            id: 'system.logininfor.browser',
+            defaultMessage: '浏览器类型',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入浏览器类型"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入浏览器类型!" defaultMessage="请输入浏览器类型!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="os"
+          label={intl.formatMessage({
+            id: 'system.logininfor.os',
+            defaultMessage: '操作系统',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入操作系统"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入操作系统!" defaultMessage="请输入操作系统!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.logininfor.status',
+            defaultMessage: '登录状态',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录状态!" defaultMessage="请输入登录状态!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="msg"
+          label={intl.formatMessage({
+            id: 'system.logininfor.msg',
+            defaultMessage: '提示消息',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入提示消息"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入提示消息!" defaultMessage="请输入提示消息!" />,                  
+            },
+          ]}
+        />
+        <ProFormTimePicker
+          name="loginTime"
+          label={intl.formatMessage({
+            id: 'system.logininfor.login_time',
+            defaultMessage: '访问时间',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入访问时间"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入访问时间!" defaultMessage="请输入访问时间!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default LogininforForm;
diff --git a/react-ui/src/pages/Monitor/Logininfor/index.tsx b/react-ui/src/pages/Monitor/Logininfor/index.tsx
new file mode 100644
index 0000000..5a39281
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Logininfor/index.tsx
@@ -0,0 +1,335 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, UnlockOutlined } from '@ant-design/icons';
+import { getLogininforList, removeLogininfor, exportLogininfor, unlockLogininfor, cleanLogininfor } from '@/services/monitor/logininfor';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Logininfor[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeLogininfor(selectedRows.map((row) => row.infoId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleClean = async () => {
+  const hide = message.loading('请稍候');
+  try {
+    const resp = await cleanLogininfor();
+    hide();
+    if (resp.code === 200) {
+      message.success('清空成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('请求失败,请重试');
+    return false;
+  }
+};
+
+const handleUnlock = async (userName: string) => {
+  const hide = message.loading('正在解锁');
+  try {
+    const resp = await unlockLogininfor(userName);
+    hide();
+    if (resp.code === 200) {
+      message.success('解锁成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('解锁失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ * @param id
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportLogininfor();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const LogininforTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const actionRef = useRef<ActionType>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Logininfor[]>([]);
+
+  const access = useAccess();
+
+  const statusOptions = {
+    0: {
+      label: '成功',
+      key: '0',
+      value: '0',
+      text: '成功',
+      status: 'success',
+      listClass: 'success'
+    },
+    1: {
+      label: '失败',
+      key: '1',
+      value: '1',
+      text: '失败',
+      status: 'error',
+      listClass: 'danger'
+    },
+  };
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Logininfor>[] = [
+    {
+      title: <FormattedMessage id="monitor.logininfor.info_id" defaultMessage="访问编号" />,
+      dataIndex: 'infoId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.ipaddr" defaultMessage="登录IP地址" />,
+      dataIndex: 'ipaddr',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.login_location" defaultMessage="登录地点" />,
+      dataIndex: 'loginLocation',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.browser" defaultMessage="浏览器类型" />,
+      dataIndex: 'browser',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.os" defaultMessage="操作系统" />,
+      dataIndex: 'os',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.status" defaultMessage="登录状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.msg" defaultMessage="提示消息" />,
+      dataIndex: 'msg',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.login_time" defaultMessage="访问时间" />,
+      dataIndex: 'loginTime',
+      valueType: 'dateTime',
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Logininfor>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="infoId"
+          key="logininforList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="clean"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认清空所有数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleClean();
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+            </Button>,
+            <Button
+              type="primary"
+              key="unlock"
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:unlock')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认解锁该用户的数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleUnlock(selectedRows[0].userName);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <UnlockOutlined />
+              <FormattedMessage id="monitor.logininfor.unlock" defaultMessage="解锁" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('monitor:logininfor:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getLogininforList({ ...params } as API.Monitor.LogininforListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('monitor:logininfor:remove')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+    </PageContainer>
+  );
+};
+
+export default LogininforTableList;
diff --git a/react-ui/src/pages/Monitor/Online/index.tsx b/react-ui/src/pages/Monitor/Online/index.tsx
new file mode 100644
index 0000000..2f81e3b
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Online/index.tsx
@@ -0,0 +1,162 @@
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import React, { useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { getOnlineUserList, forceLogout } from '@/services/monitor/online';
+import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
+import { DeleteOutlined } from '@ant-design/icons';
+ 
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+
+const handleForceLogout = async (selectedRow: API.Monitor.OnlineUserType) => {
+  const hide = message.loading('正在强制下线');
+  try {
+    await forceLogout(selectedRow.tokenId);
+    hide();
+    message.success('强制下线成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('强制下线失败,请重试');
+    return false;
+  }
+};
+
+const OnlineUserTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+  const actionRef = useRef<ActionType>();
+  const access = useAccess();
+  const intl = useIntl();
+
+  useEffect(() => {}, []);
+
+  const columns: ProColumns<API.Monitor.OnlineUserType>[] = [
+    {
+      title: <FormattedMessage id="monitor.online.user.token_id" defaultMessage="会话编号" />,
+      dataIndex: 'tokenId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.dept_name" defaultMessage="部门名称" />,
+      dataIndex: 'deptName',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.ipaddr" defaultMessage="登录IP地址" />,
+      dataIndex: 'ipaddr',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.login_location" defaultMessage="登录地点" />,
+      dataIndex: 'loginLocation',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.browser" defaultMessage="浏览器类型" />,
+      dataIndex: 'browser',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.os" defaultMessage="操作系统" />,
+      dataIndex: 'os',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.login_time" defaultMessage="登录时间" />,
+      dataIndex: 'loginTime',
+      valueType: 'dateRange',
+      render: (_, record) => <span>{record.loginTime}</span>,
+      hideInSearch: true,
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '60px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          icon=<DeleteOutlined />
+          hidden={!access.hasPerms('monitor:online:forceLogout')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '强踢',
+              content: '确定强制将该用户踢下线吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleForceLogout(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          强退
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.OnlineUserType>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="tokenId"
+          key="logininforList"
+          search={{
+            labelWidth: 120,
+          }}
+          request={(params) =>
+            getOnlineUserList({ ...params } as API.Monitor.OnlineUserListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+        />
+      </div>
+  );
+};
+
+export default OnlineUserTableList;
diff --git a/react-ui/src/pages/Monitor/Operlog/detail.tsx b/react-ui/src/pages/Monitor/Operlog/detail.tsx
new file mode 100644
index 0000000..f247522
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Operlog/detail.tsx
@@ -0,0 +1,113 @@
+import React from 'react';
+import { Descriptions, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { getValueEnumLabel } from '@/utils/options';
+
+export type OperlogFormData = Record<string, unknown> & Partial<API.Monitor.Operlog>;
+
+export type OperlogFormProps = {
+  onCancel: (flag?: boolean, formVals?: OperlogFormData) => void;
+  onSubmit: (values: OperlogFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Operlog>;
+  businessTypeOptions: DictValueEnumObj;
+  operatorTypeOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+};
+
+const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => {
+
+  const { values, businessTypeOptions, operatorTypeOptions, statusOptions, } = props;
+
+  const intl = useIntl();
+  const handleOk = () => {};
+  const handleCancel = () => {
+    props.onCancel();
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'monitor.operlog.title',
+        defaultMessage: '编辑操作日志记录',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Descriptions column={24}>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.module" defaultMessage="操作模块" />}
+        >
+          {`${values.title}/${getValueEnumLabel(businessTypeOptions, values.businessType)}`}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />}
+        >
+          {values.requestMethod}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />}
+        >
+          {`${values.operName}/${values.operIp}`}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />}
+        >
+          {getValueEnumLabel(operatorTypeOptions, values.operatorType)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.method" defaultMessage="方法名称" />}
+        >
+          {values.method}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.oper_url" defaultMessage="请求URL" />}
+        >
+          {values.operUrl}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.oper_param" defaultMessage="请求参数" />}
+        >
+          {values.operParam}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.json_result" defaultMessage="返回参数" />}
+        >
+          {values.jsonResult}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.error_msg" defaultMessage="错误消息" />}
+        >
+          {values.errorMsg}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />}
+        >
+          {getValueEnumLabel(statusOptions, values.status)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />}
+        >
+          {values.operTime?.toString()}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default OperlogDetailForm;
diff --git a/react-ui/src/pages/Monitor/Operlog/index.tsx b/react-ui/src/pages/Monitor/Operlog/index.tsx
new file mode 100644
index 0000000..9440bc8
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Operlog/index.tsx
@@ -0,0 +1,365 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getOperlogList, removeOperlog, addOperlog, updateOperlog, exportOperlog } from '@/services/monitor/operlog';
+import UpdateForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Operlog) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addOperlog({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Operlog) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateOperlog(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Operlog[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeOperlog(selectedRows.map((row) => row.operId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportOperlog();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const OperlogTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.Monitor.Operlog>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Operlog[]>([]);
+
+  const [businessTypeOptions, setBusinessTypeOptions] = useState<any>([]);
+  const [operatorTypeOptions, setOperatorTypeOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_oper_type', true).then((data) => {
+      setBusinessTypeOptions(data);
+    });
+    getDictValueEnum('sys_oper_type', true).then((data) => {
+      setOperatorTypeOptions(data);
+    });
+    getDictValueEnum('sys_common_status', true).then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Operlog>[] = [
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_id" defaultMessage="日志主键" />,
+      dataIndex: 'operId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.title" defaultMessage="操作模块" />,
+      dataIndex: 'title',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.business_type" defaultMessage="业务类型" />,
+      dataIndex: 'businessType',
+      valueType: 'select',
+      valueEnum: businessTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={businessTypeOptions} value={record.businessType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />,
+      dataIndex: 'requestMethod',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />,
+      dataIndex: 'operatorType',
+      valueType: 'select',
+      valueEnum: operatorTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={operatorTypeOptions} value={record.operatorType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />,
+      dataIndex: 'operName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_ip" defaultMessage="主机地址" />,
+      dataIndex: 'operIp',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_location" defaultMessage="操作地点" />,
+      dataIndex: 'operLocation',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag key="status" enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />,
+      dataIndex: 'operTime',
+      valueType: 'dateTime',
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:operlog:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          详细
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Operlog>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="operId"
+          key="operlogList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:operlog:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:operlog:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:operlog:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getOperlogList({ ...params } as API.Monitor.OperlogListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:operlog:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.operId) {
+            success = await handleUpdate({ ...values } as API.Monitor.Operlog);
+          } else {
+            success = await handleAdd({ ...values } as API.Monitor.Operlog);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        businessTypeOptions={businessTypeOptions}
+        operatorTypeOptions={operatorTypeOptions}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default OperlogTableList;
diff --git a/react-ui/src/pages/Monitor/Server/index.tsx b/react-ui/src/pages/Monitor/Server/index.tsx
new file mode 100644
index 0000000..9ea3969
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Server/index.tsx
@@ -0,0 +1,267 @@
+import React, { useEffect, useState } from 'react';
+import { getServerInfo } from '@/services/monitor/server';
+import { Card, Col, Row, Table } from 'antd';
+import { FormattedMessage } from '@umijs/max';
+import styles from './style.less';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+
+const columns = [
+	{
+		title: '属性',
+		dataIndex: 'name',
+		key: 'name',
+	},
+	{
+		title: '值',
+		dataIndex: 'value',
+		key: 'value',
+	},
+];
+
+const memColumns = [
+	{
+		title: '属性',
+		dataIndex: 'name',
+		key: 'name',
+	},
+	{
+		title: '内存',
+		dataIndex: 'mem',
+		key: 'mem',
+	},
+	{
+		title: 'JVM',
+		dataIndex: 'jvm',
+		key: 'jvm',
+	},
+];
+
+const hostColumns = [
+	{
+		title: 'col1',
+		dataIndex: 'col1',
+		key: 'col1',
+	},
+	{
+		title: 'col2',
+		dataIndex: 'col2',
+		key: 'col2',
+	},
+	{
+		title: 'col3',
+		dataIndex: 'col3',
+		key: 'col3',
+	},
+	{
+		title: 'col4',
+		dataIndex: 'col4',
+		key: 'col4',
+	},
+];
+
+const diskColumns = [
+	{
+		title: <FormattedMessage id="monitor.server.disk.dirName" defaultMessage="盘符路径" />,
+		dataIndex: 'dirName',
+		key: 'dirName',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.sysTypeName" defaultMessage="文件系统" />,
+		dataIndex: 'sysTypeName',
+		key: 'sysTypeName',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.typeName" defaultMessage="盘符类型" />,
+		dataIndex: 'typeName',
+		key: 'typeName',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.total" defaultMessage="总大小" />,
+		dataIndex: 'total',
+		key: 'total',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.free" defaultMessage="可用大小" />,
+		dataIndex: 'free',
+		key: 'free',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.used" defaultMessage="已用大小" />,
+		dataIndex: 'used',
+		key: 'used',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.usage" defaultMessage="已用百分比" />,
+		dataIndex: 'usage',
+		key: 'usage',
+	},
+];
+
+const ServerInfo: React.FC = () => {
+	const [cpuData, setCpuData] = useState<API.Monitor.CpuRowType[]>([]);
+	const [memData, setMemData] = useState<API.Monitor.MemRowType[]>([]);
+	const [hostData, setHostData] = useState<any>([]);
+	const [jvmData, setJvmData] = useState<any>([]);
+	const [diskData, setDiskData] = useState<any>([]);
+
+	useEffect(() => {
+		getServerInfo().then((res: API.Monitor.ServerInfoResponseType) => {
+			if (res.code === 200) {
+				// const cpuinfo: CpuRowType[] = [];
+				// Object.keys(res.data.cpu).forEach((item: any) => {
+				//   cpuinfo.push({
+				//     name: item,
+				//     value: res.data.cpu[item],
+				//   });
+				// });
+				// setCpuData(cpuinfo);
+
+				const cpuinfo: API.Monitor.CpuRowType[] = [];
+				cpuinfo.push({ name: '核心数', value: res.data.cpu.cpuNum });
+				cpuinfo.push({ name: '用户使用率', value: `${res.data.cpu.used}%` });
+				cpuinfo.push({ name: '系统使用率', value: `${res.data.cpu.sys}%` });
+				cpuinfo.push({ name: '当前空闲率', value: `${res.data.cpu.free}%` });
+
+				setCpuData(cpuinfo);
+
+				const memDatas: API.Monitor.MemRowType[] = [];
+				memDatas.push({
+					name: '总内存',
+					mem: `${res.data.mem.total}G`,
+					jvm: `${res.data.jvm.total}M`,
+				});
+				memDatas.push({
+					name: '已用内存',
+					mem: `${res.data.mem.used}G`,
+					jvm: `${res.data.jvm.used}M`,
+				});
+				memDatas.push({
+					name: '剩余内存',
+					mem: `${res.data.mem.free}G`,
+					jvm: `${res.data.jvm.free}M`,
+				});
+				memDatas.push({
+					name: '使用率',
+					mem: `${res.data.mem.usage}%`,
+					jvm: `${res.data.jvm.usage}%`,
+				});
+				setMemData(memDatas);
+
+				const hostinfo = [];
+				hostinfo.push({
+					col1: '服务器名称',
+					col2: res.data.sys.computerName,
+					col3: '操作系统',
+					col4: res.data.sys.osName,
+				});
+				hostinfo.push({
+					col1: '服务器IP',
+					col2: res.data.sys.computerIp,
+					col3: '系统架构',
+					col4: res.data.sys.osArch,
+				});
+				setHostData(hostinfo);
+
+				const jvminfo = [];
+				jvminfo.push({
+					col1: 'Java名称',
+					col2: res.data.jvm.name,
+					col3: 'Java版本',
+					col4: res.data.jvm.version,
+				});
+				jvminfo.push({
+					col1: '启动时间',
+					col2: res.data.jvm.startTime,
+					col3: '运行时长',
+					col4: res.data.jvm.runTime,
+				});
+				jvminfo.push({
+					col1: '安装路径',
+					col2: res.data.jvm.home,
+					col3: '项目路径',
+					col4: res.data.sys.userDir,
+				});
+				setJvmData(jvminfo);
+
+				const diskinfo = res.data.sysFiles.map((item: API.Monitor.DiskInfoType) => {
+					return {
+						dirName: item.dirName,
+						sysTypeName: item.sysTypeName,
+						typeName: item.typeName,
+						total: item.total,
+						free: item.free,
+						used: item.used,
+						usage: `${item.usage}%`,
+					};
+				});
+				setDiskData(diskinfo);
+			}
+		});
+	}, []);
+
+	return (
+		<div>
+			<Row gutter={[24, 24]}>
+				<Col span={12}>
+					<Card title="CPU" className={styles.card}>
+						<Table rowKey="name" pagination={false} showHeader={false} dataSource={cpuData} columns={columns} />
+					</Card>
+				</Col>
+				<Col span={12}>
+					<Card title="内存" className={styles.card}>
+						<Table
+							rowKey="name"
+							pagination={false}
+							showHeader={false}
+							dataSource={memData}
+							columns={memColumns}
+						/>
+					</Card>
+				</Col>
+			</Row>
+			<Row gutter={[16, 16]}>
+				<Col span={24}>
+					<Card title="服务器信息" className={styles.card}>
+						<Table
+							rowKey="col1"
+							pagination={false}
+							showHeader={false}
+							dataSource={hostData}
+							columns={hostColumns}
+						/>
+					</Card>
+				</Col>
+			</Row>
+			<Row gutter={[16, 16]}>
+				<Col span={24}>
+					<Card title="Java虚拟机信息" className={styles.card}>
+						<Table
+							rowKey="col1"
+							pagination={false}
+							showHeader={false}
+							dataSource={jvmData}
+							columns={hostColumns}
+						/>
+					</Card>
+				</Col>
+			</Row>
+			<Row gutter={[16, 16]}>
+				<Col span={24}>
+					<Card title="磁盘状态" className={styles.card}>
+						<Table rowKey="dirName" pagination={false} dataSource={diskData} columns={diskColumns} />
+					</Card>
+				</Col>
+			</Row>
+		</div>
+	);
+};
+
+export default ServerInfo;
diff --git a/react-ui/src/pages/Monitor/Server/style.less b/react-ui/src/pages/Monitor/Server/style.less
new file mode 100644
index 0000000..d244a53
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Server/style.less
@@ -0,0 +1,11 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+.card {
+  margin-bottom: 12px;
+}
diff --git a/react-ui/src/pages/System/Config/edit.tsx b/react-ui/src/pages/System/Config/edit.tsx
new file mode 100644
index 0000000..6cb3942
--- /dev/null
+++ b/react-ui/src/pages/System/Config/edit.tsx
@@ -0,0 +1,172 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormTextArea,
+  ProFormRadio,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type ConfigFormData = Record<string, unknown> & Partial<API.System.Config>;
+
+export type ConfigFormProps = {
+  onCancel: (flag?: boolean, formVals?: ConfigFormData) => void;
+  onSubmit: (values: ConfigFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Config>;
+  configTypeOptions: DictValueEnumObj;
+};
+
+const ConfigForm: React.FC<ConfigFormProps> = (props) => {
+  const [form] = Form.useForm();
+  
+  const { configTypeOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			configId: props.values.configId,
+			configName: props.values.configName,
+			configKey: props.values.configKey,
+			configValue: props.values.configValue,
+			configType: props.values.configType,
+			createBy: props.values.createBy,
+			createTime: props.values.createTime,
+			updateBy: props.values.updateBy,
+			updateTime: props.values.updateTime,
+			remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as ConfigFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.config.title',
+        defaultMessage: '编辑参数配置',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="configId"
+          label={intl.formatMessage({
+            id: 'system.config.config_id',
+            defaultMessage: '参数主键',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入参数主键"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入参数主键!" defaultMessage="请输入参数主键!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="configName"
+          label={intl.formatMessage({
+            id: 'system.config.config_name',
+            defaultMessage: '参数名称',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入参数名称"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入参数名称!" defaultMessage="请输入参数名称!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="configKey"
+          label={intl.formatMessage({
+            id: 'system.config.config_key',
+            defaultMessage: '参数键名',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入参数键名"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入参数键名!" defaultMessage="请输入参数键名!" />,                  
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="configValue"
+          label={intl.formatMessage({
+            id: 'system.config.config_value',
+            defaultMessage: '参数键值',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入参数键值"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入参数键值!" defaultMessage="请输入参数键值!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={configTypeOptions}
+          name="configType"
+          label={intl.formatMessage({
+            id: 'system.config.config_type',
+            defaultMessage: '系统内置',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入系统内置"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入系统内置!" defaultMessage="请输入系统内置!" />,                  
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.config.remark',
+            defaultMessage: '备注',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default ConfigForm;
diff --git a/react-ui/src/pages/System/Config/index.tsx b/react-ui/src/pages/System/Config/index.tsx
new file mode 100644
index 0000000..1428891
--- /dev/null
+++ b/react-ui/src/pages/System/Config/index.tsx
@@ -0,0 +1,397 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined, DownloadOutlined } from '@ant-design/icons';
+import { getConfigList, removeConfig, addConfig, updateConfig, exportConfig, refreshConfigCache } from '@/services/system/config';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Config) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addConfig({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Config) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateConfig(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Config[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeConfig(selectedRows.map((row) => row.configId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Config) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.configId];
+    const resp = await removeConfig(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportConfig();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+const handleRefreshCache = async () => {
+  const hide = message.loading('正在刷新');
+  try {
+    await refreshConfigCache();
+    hide();
+    message.success('刷新成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('刷新失败,请重试');
+    return false;
+  }
+};
+
+const ConfigTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Config>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Config[]>([]);
+
+  const [configTypeOptions, setConfigTypeOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_yes_no').then((data) => {
+      setConfigTypeOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Config>[] = [
+    {
+      title: <FormattedMessage id="system.config.config_id" defaultMessage="参数主键" />,
+      dataIndex: 'configId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.config.config_name" defaultMessage="参数名称" />,
+      dataIndex: 'configName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.config.config_key" defaultMessage="参数键名" />,
+      dataIndex: 'configKey',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.config.config_value" defaultMessage="参数键值" />,
+      dataIndex: 'configValue',
+      valueType: 'textarea',
+    },
+    {
+      title: <FormattedMessage id="system.config.config_type" defaultMessage="系统内置" />,
+      dataIndex: 'configType',
+      valueType: 'select',
+      valueEnum: configTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={configTypeOptions} value={record.configType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.config.remark" defaultMessage="备注" />,
+      dataIndex: 'remark',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:config:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:config:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Config>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="configId"
+          key="configList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:config:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:config:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:config:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <DownloadOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+            <Button
+              type="primary"
+              key="refresh"
+              danger
+              hidden={!access.hasPerms('system:config:remove')}
+              onClick={async () => {
+                handleRefreshCache();
+              }}
+            >
+              <ReloadOutlined />
+              <FormattedMessage id="system.config.refreshCache" defaultMessage="刷新缓存" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getConfigList({ ...params } as API.System.ConfigListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:config:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.configId) {
+            success = await handleUpdate({ ...values } as API.System.Config);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Config);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        configTypeOptions={configTypeOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default ConfigTableList;
diff --git a/react-ui/src/pages/System/Dept/edit.tsx b/react-ui/src/pages/System/Dept/edit.tsx
new file mode 100644
index 0000000..cb64813
--- /dev/null
+++ b/react-ui/src/pages/System/Dept/edit.tsx
@@ -0,0 +1,212 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTreeSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DeptFormData = Record<string, unknown> & Partial<API.System.Dept>;
+
+export type DeptFormProps = {
+  onCancel: (flag?: boolean, formVals?: DeptFormData) => void;
+  onSubmit: (values: DeptFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Dept>;
+  deptTree: DataNode[];
+  statusOptions: DictValueEnumObj;
+};
+
+const DeptForm: React.FC<DeptFormProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const { statusOptions, deptTree } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      deptId: props.values.deptId,
+      parentId: props.values.parentId,
+      ancestors: props.values.ancestors,
+      deptName: props.values.deptName,
+      orderNum: props.values.orderNum,
+      leader: props.values.leader,
+      phone: props.values.phone,
+      email: props.values.email,
+      status: props.values.status,
+      delFlag: props.values.delFlag,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as DeptFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.dept.title',
+        defaultMessage: '编辑部门',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="deptId"
+          label={intl.formatMessage({
+            id: 'system.dept.dept_id',
+            defaultMessage: '部门id',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入部门id"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入部门id!" defaultMessage="请输入部门id!" />,
+            },
+          ]}
+        />
+        <ProFormTreeSelect
+          name="parentId"
+          label={intl.formatMessage({
+            id: 'system.dept.parent_dept',
+            defaultMessage: '上级部门:',
+          })}
+          request={async () => {
+            return deptTree;
+          }}
+          placeholder="请选择上级部门"
+          rules={[
+            {
+              required: true,
+              message: (
+                <FormattedMessage id="请输入用户昵称!" defaultMessage="请选择上级部门!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormText
+          name="deptName"
+          label={intl.formatMessage({
+            id: 'system.dept.dept_name',
+            defaultMessage: '部门名称',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入部门名称"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入部门名称!" defaultMessage="请输入部门名称!" />,
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="orderNum"
+          label={intl.formatMessage({
+            id: 'system.dept.order_num',
+            defaultMessage: '显示顺序',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入显示顺序"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="leader"
+          label={intl.formatMessage({
+            id: 'system.dept.leader',
+            defaultMessage: '负责人',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入负责人"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入负责人!" defaultMessage="请输入负责人!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="phone"
+          label={intl.formatMessage({
+            id: 'system.dept.phone',
+            defaultMessage: '联系电话',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入联系电话"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入联系电话!" defaultMessage="请输入联系电话!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="email"
+          label={intl.formatMessage({
+            id: 'system.dept.email',
+            defaultMessage: '邮箱',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入邮箱"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.dept.status',
+            defaultMessage: '部门状态',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入部门状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入部门状态!" defaultMessage="请输入部门状态!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default DeptForm;
diff --git a/react-ui/src/pages/System/Dept/index.tsx b/react-ui/src/pages/System/Dept/index.tsx
new file mode 100644
index 0000000..3a1c5de
--- /dev/null
+++ b/react-ui/src/pages/System/Dept/index.tsx
@@ -0,0 +1,346 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDeptList, removeDept, addDept, updateDept, getDeptListExcludeChild } from '@/services/system/dept';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { buildTreeData } from '@/utils/tree';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Dept) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addDept({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Dept) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateDept(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Dept[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeDept(selectedRows.map((row) => row.deptId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Dept) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.deptId];
+    const resp = await removeDept(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+
+const DeptTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Dept>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Dept[]>([]);
+
+  const [deptTree, setDeptTree] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Dept>[] = [
+    {
+      title: <FormattedMessage id="system.dept.dept_name" defaultMessage="部门名称" />,
+      dataIndex: 'deptName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dept.order_num" defaultMessage="显示顺序" />,
+      dataIndex: 'orderNum',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dept.status" defaultMessage="部门状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:dept:edit')}
+          onClick={() => {
+            getDeptListExcludeChild(record.deptId).then((res) => {
+              if (res.code === 200) {
+                let depts = buildTreeData(res.data, 'deptId', 'deptName', '', '', '');
+                if(depts.length === 0) {
+                  depts = [{ id: 0, title: '无上级', children: undefined, key: 0, value: 0 }];
+                }
+                setDeptTree(depts);
+                setModalVisible(true);
+                setCurrentRow(record);
+              } else {
+                message.warning(res.msg);
+              }
+            });
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:dept:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Dept>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="deptId"
+          key="deptList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:dept:add')}
+              onClick={async () => {
+                getDeptList().then((res) => {
+                  if (res.code === 200) {
+                    setDeptTree(buildTreeData(res.data, 'deptId', 'deptName', '', '', ''));
+                    setCurrentRow(undefined);
+                    setModalVisible(true);
+                  } else {
+                    message.warning(res.msg);
+                  }
+                });
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:dept:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getDeptList({ ...params } as API.System.DeptListParams).then((res) => {
+              const result = {
+                data: buildTreeData(res.data, 'deptId', '', '', '', ''),
+                total: res.data.length,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:dept:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.deptId) {
+            success = await handleUpdate({ ...values } as API.System.Dept);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Dept);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        deptTree={deptTree}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default DeptTableList;
diff --git a/react-ui/src/pages/System/Dict/edit.tsx b/react-ui/src/pages/System/Dict/edit.tsx
new file mode 100644
index 0000000..882c1cb
--- /dev/null
+++ b/react-ui/src/pages/System/Dict/edit.tsx
@@ -0,0 +1,152 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTextArea,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DictTypeFormData = Record<string, unknown> & Partial<API.System.DictType>;
+
+export type DictTypeFormProps = {
+  onCancel: (flag?: boolean, formVals?: DictTypeFormData) => void;
+  onSubmit: (values: DictTypeFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.DictType>;
+  statusOptions: DictValueEnumObj;
+};
+
+const DictTypeForm: React.FC<DictTypeFormProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const { statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			dictId: props.values.dictId,
+			dictName: props.values.dictName,
+			dictType: props.values.dictType,
+			status: props.values.status,
+			createBy: props.values.createBy,
+			createTime: props.values.createTime,
+			updateBy: props.values.updateBy,
+			updateTime: props.values.updateTime,
+			remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as DictTypeFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.dict.title',
+        defaultMessage: '编辑字典类型',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="dictId"
+          label={intl.formatMessage({
+            id: 'system.dict.dict_id',
+            defaultMessage: '字典主键',
+          })}
+          placeholder="请输入字典主键"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典主键!" defaultMessage="请输入字典主键!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictName"
+          label={intl.formatMessage({
+            id: 'system.dict.dict_name',
+            defaultMessage: '字典名称',
+          })}
+          placeholder="请输入字典名称"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典名称!" defaultMessage="请输入字典名称!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictType"
+          label={intl.formatMessage({
+            id: 'system.dict.dict_type',
+            defaultMessage: '字典类型',
+          })}
+          placeholder="请输入字典类型"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典类型!" defaultMessage="请输入字典类型!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.dict.status',
+            defaultMessage: '状态',
+          })}
+          initialValue={'0'}
+          placeholder="请输入状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.dict.remark',
+            defaultMessage: '备注',
+          })}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default DictTypeForm;
diff --git a/react-ui/src/pages/System/Dict/index.tsx b/react-ui/src/pages/System/Dict/index.tsx
new file mode 100644
index 0000000..ccdbc2a
--- /dev/null
+++ b/react-ui/src/pages/System/Dict/index.tsx
@@ -0,0 +1,394 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDictTypeList, removeDictType, addDictType, updateDictType, exportDictType } from '@/services/system/dict';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.DictType) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addDictType({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.DictType) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateDictType(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.DictType[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeDictType(selectedRows.map((row) => row.dictId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.DictType) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.dictId];
+    const resp = await removeDictType(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportDictType();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const DictTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.DictType>();
+  const [selectedRows, setSelectedRows] = useState<API.System.DictType[]>([]);
+
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.DictType>[] = [
+    {
+      title: <FormattedMessage id="system.dict.dict_id" defaultMessage="字典编号" />,
+      dataIndex: 'dictId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.dict.dict_name" defaultMessage="字典名称" />,
+      dataIndex: 'dictName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dict.dict_type" defaultMessage="字典类型" />,
+      dataIndex: 'dictType',
+      valueType: 'text',
+      render: (dom, record) => {
+        return (
+          <a
+            onClick={() => {
+              history.push(`/system/dict-data/index/${record.dictId}`);
+            }}
+          >
+            {dom}
+          </a>
+        );
+      },
+    },
+    {
+      title: <FormattedMessage id="system.dict.status" defaultMessage="状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.dict.remark" defaultMessage="备注" />,
+      dataIndex: 'remark',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:dictType:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:dictType:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.DictType>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="dictId"
+          key="dictTypeList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:dictType:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:dictType:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:dictType:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getDictTypeList({ ...params } as API.System.DictTypeListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:dictType:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.dictId) {
+            success = await handleUpdate({ ...values } as API.System.DictType);
+          } else {
+            success = await handleAdd({ ...values } as API.System.DictType);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default DictTableList;
diff --git a/react-ui/src/pages/System/DictData/edit.tsx b/react-ui/src/pages/System/DictData/edit.tsx
new file mode 100644
index 0000000..188e01b
--- /dev/null
+++ b/react-ui/src/pages/System/DictData/edit.tsx
@@ -0,0 +1,252 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormSelect,
+  ProFormRadio,
+  ProFormTextArea,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DataFormData = Record<string, unknown> & Partial<API.System.DictData>;
+
+export type DataFormProps = {
+  onCancel: (flag?: boolean, formVals?: DataFormData) => void;
+  onSubmit: (values: DataFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.DictData>;
+  statusOptions: DictValueEnumObj;
+};
+
+const DictDataForm: React.FC<DataFormProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const { statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      dictCode: props.values.dictCode,
+      dictSort: props.values.dictSort,
+      dictLabel: props.values.dictLabel,
+      dictValue: props.values.dictValue,
+      dictType: props.values.dictType,
+      cssClass: props.values.cssClass,
+      listClass: props.values.listClass,
+      isDefault: props.values.isDefault,
+      status: props.values.status,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as DataFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.dict.data.title',
+        defaultMessage: '编辑字典数据',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="dictCode"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_code',
+            defaultMessage: '字典编码',
+          })}
+          colProps={{ md: 24, xl: 24 }}
+          placeholder="请输入字典编码"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典编码!" defaultMessage="请输入字典编码!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictType"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_type',
+            defaultMessage: '字典类型',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入字典类型"
+          disabled
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典类型!" defaultMessage="请输入字典类型!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictLabel"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_label',
+            defaultMessage: '字典标签',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入字典标签"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典标签!" defaultMessage="请输入字典标签!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictValue"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_value',
+            defaultMessage: '字典键值',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入字典键值"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典键值!" defaultMessage="请输入字典键值!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="cssClass"
+          label={intl.formatMessage({
+            id: 'system.dict.data.css_class',
+            defaultMessage: '样式属性',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入样式属性"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入样式属性!" defaultMessage="请输入样式属性!" />,
+            },
+          ]}
+        />
+        <ProFormSelect
+          name="listClass"
+          label={intl.formatMessage({
+            id: 'system.dict.data.list_class',
+            defaultMessage: '回显样式',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入回显样式"
+          valueEnum={{
+            'default': '默认',
+            'primary': '主要',
+            'success': '成功',
+            'info': '信息',
+            'warning': '警告',
+            'danger': '危险',
+          }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入回显样式!" defaultMessage="请输入回显样式!" />,
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="dictSort"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_sort',
+            defaultMessage: '字典排序',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入字典排序"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典排序!" defaultMessage="请输入字典排序!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          name="isDefault"
+          label={intl.formatMessage({
+            id: 'system.dict.data.is_default',
+            defaultMessage: '是否默认',
+          })}
+          valueEnum={{
+            'Y': '是',
+            'N': '否',
+          }}
+          initialValue={'N'}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入是否默认"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入是否默认!" defaultMessage="请输入是否默认!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.dict.data.status',
+            defaultMessage: '状态',
+          })}
+          initialValue={'0'}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.dict.data.remark',
+            defaultMessage: '备注',
+          })}
+          colProps={{ md: 24, xl: 24 }}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default DictDataForm;
diff --git a/react-ui/src/pages/System/DictData/index.tsx b/react-ui/src/pages/System/DictData/index.tsx
new file mode 100644
index 0000000..2bf1069
--- /dev/null
+++ b/react-ui/src/pages/System/DictData/index.tsx
@@ -0,0 +1,439 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history, useParams } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDictDataList, removeDictData, addDictData, updateDictData, exportDictData } from '@/services/system/dictdata';
+import UpdateForm from './edit';
+import { getDictValueEnum, getDictType, getDictTypeOptionSelect } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.DictData) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addDictData({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.DictData) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateDictData(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.DictData[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeDictData(selectedRows.map((row) => row.dictCode).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.DictData) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.dictCode];
+    const resp = await removeDictData(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportDictData();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+export type DictTypeArgs = {
+  id: string;
+};
+
+
+const DictDataTableList: React.FC = () => {
+
+  const formTableRef = useRef<FormInstance>();
+
+  const [dictId, setDictId] = useState<string>('');
+  const [dictType, setDictType] = useState<string>('');
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.DictData>();
+  const [selectedRows, setSelectedRows] = useState<API.System.DictData[]>([]);
+
+  const [dictTypeOptions, setDictTypeOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  const params = useParams();
+  if (params.id === undefined) {
+    history.push('/system/dict');
+  }
+  const id = params.id || '0';
+
+  useEffect(() => {
+    if (dictId !== id) {
+      setDictId(id);
+      getDictTypeOptionSelect().then((res) => {
+        if (res.code === 200) {
+          const opts: any = {};
+          res.data.forEach((item: any) => {
+            opts[item.dictType] = item.dictName;
+          });
+          setDictTypeOptions(opts);
+        }
+      });
+      getDictValueEnum('sys_normal_disable').then((data) => {
+        setStatusOptions(data);
+      });
+      getDictType(id).then((res) => {
+        if (res.code === 200) {
+          setDictType(res.data.dictType);
+          formTableRef.current?.setFieldsValue({
+            dictType: res.data.dictType,
+          });
+          actionRef.current?.reloadAndRest?.();
+        } else {
+          message.error(res.msg);
+        }
+      });
+    }
+  }, [dictId, dictType, params]);
+
+  const columns: ProColumns<API.System.DictData>[] = [
+    {
+      title: <FormattedMessage id="system.dict.data.dict_code" defaultMessage="字典编码" />,
+      dataIndex: 'dictCode',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.dict_label" defaultMessage="字典标签" />,
+      dataIndex: 'dictLabel',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.dict_type" defaultMessage="字典类型" />,
+      dataIndex: 'dictType',
+      valueType: 'select',
+      hideInTable: true,
+      valueEnum: dictTypeOptions,
+      search: {
+        transform: (value) => {
+          setDictType(value);
+          return value;
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.dict_value" defaultMessage="字典键值" />,
+      dataIndex: 'dictValue',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.dict_sort" defaultMessage="字典排序" />,
+      dataIndex: 'dictSort',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.status" defaultMessage="状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.remark" defaultMessage="备注" />,
+      dataIndex: 'remark',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:data:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:data:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.DictData>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="dictCode"
+          key="dataList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:data:add')}
+              onClick={async () => {
+                setCurrentRow({ dictType: dictType, isDefault: 'N', status: '0' } as API.System.DictData);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:data:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:data:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getDictDataList({ ...params, dictType } as API.System.DictDataListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:data:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.dictCode) {
+            success = await handleUpdate({ ...values } as API.System.DictData);
+          } else {
+            success = await handleAdd({ ...values } as API.System.DictData);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default DictDataTableList;
diff --git a/react-ui/src/pages/System/Logininfor/edit.tsx b/react-ui/src/pages/System/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/react-ui/src/pages/System/Logininfor/edit.tsx
@@ -0,0 +1,216 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTimePicker,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type LogininforFormData = Record<string, unknown> & Partial<API.Monitor.Logininfor>;
+
+export type LogininforFormProps = {
+  onCancel: (flag?: boolean, formVals?: LogininforFormData) => void;
+  onSubmit: (values: LogininforFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Logininfor>;
+  statusOptions: DictValueEnumObj;
+};
+
+const LogininforForm: React.FC<LogininforFormProps> = (props) => {
+  const [form] = Form.useForm();
+  
+  const { statusOptions, } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			infoId: props.values.infoId,
+			userName: props.values.userName,
+			ipaddr: props.values.ipaddr,
+			loginLocation: props.values.loginLocation,
+			browser: props.values.browser,
+			os: props.values.os,
+			status: props.values.status,
+			msg: props.values.msg,
+			loginTime: props.values.loginTime,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+    form.resetFields();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as LogininforFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.logininfor.title',
+        defaultMessage: '编辑系统访问记录',
+      })}
+      open={props.open}
+      destroyOnClose
+      forceRender
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="infoId"
+          label={intl.formatMessage({
+            id: 'system.logininfor.info_id',
+            defaultMessage: '访问编号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入访问编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入访问编号!" defaultMessage="请输入访问编号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="userName"
+          label={intl.formatMessage({
+            id: 'system.logininfor.user_name',
+            defaultMessage: '用户账号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入用户账号"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入用户账号!" defaultMessage="请输入用户账号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="ipaddr"
+          label={intl.formatMessage({
+            id: 'system.logininfor.ipaddr',
+            defaultMessage: '登录IP地址',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录IP地址"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录IP地址!" defaultMessage="请输入登录IP地址!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="loginLocation"
+          label={intl.formatMessage({
+            id: 'system.logininfor.login_location',
+            defaultMessage: '登录地点',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录地点"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录地点!" defaultMessage="请输入登录地点!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="browser"
+          label={intl.formatMessage({
+            id: 'system.logininfor.browser',
+            defaultMessage: '浏览器类型',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入浏览器类型"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入浏览器类型!" defaultMessage="请输入浏览器类型!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="os"
+          label={intl.formatMessage({
+            id: 'system.logininfor.os',
+            defaultMessage: '操作系统',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入操作系统"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入操作系统!" defaultMessage="请输入操作系统!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.logininfor.status',
+            defaultMessage: '登录状态',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录状态!" defaultMessage="请输入登录状态!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="msg"
+          label={intl.formatMessage({
+            id: 'system.logininfor.msg',
+            defaultMessage: '提示消息',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入提示消息"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入提示消息!" defaultMessage="请输入提示消息!" />,                  
+            },
+          ]}
+        />
+        <ProFormTimePicker
+          name="loginTime"
+          label={intl.formatMessage({
+            id: 'system.logininfor.login_time',
+            defaultMessage: '访问时间',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入访问时间"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入访问时间!" defaultMessage="请输入访问时间!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default LogininforForm;
diff --git a/react-ui/src/pages/System/Logininfor/index.tsx b/react-ui/src/pages/System/Logininfor/index.tsx
new file mode 100644
index 0000000..ee5c1e5
--- /dev/null
+++ b/react-ui/src/pages/System/Logininfor/index.tsx
@@ -0,0 +1,321 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, UnlockOutlined } from '@ant-design/icons';
+import { getLogininforList, removeLogininfor, exportLogininfor, unlockLogininfor, cleanLogininfor } from '@/services/monitor/logininfor';
+import DictTag from '@/components/DictTag';
+import { getDictValueEnum } from '@/services/system/dict';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Logininfor[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeLogininfor(selectedRows.map((row) => row.infoId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleClean = async () => {
+  const hide = message.loading('请稍候');
+  try {
+    const resp = await cleanLogininfor();
+    hide();
+    if (resp.code === 200) {
+      message.success('清空成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('请求失败,请重试');
+    return false;
+  }
+};
+
+const handleUnlock = async (userName: string) => {
+  const hide = message.loading('正在解锁');
+  try {
+    const resp = await unlockLogininfor(userName);
+    hide();
+    if (resp.code === 200) {
+      message.success('解锁成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('解锁失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ * @param id
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportLogininfor();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const LogininforTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const actionRef = useRef<ActionType>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Logininfor[]>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_common_status', true).then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Logininfor>[] = [
+    {
+      title: <FormattedMessage id="monitor.logininfor.info_id" defaultMessage="访问编号" />,
+      dataIndex: 'infoId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.ipaddr" defaultMessage="登录IP地址" />,
+      dataIndex: 'ipaddr',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.login_location" defaultMessage="登录地点" />,
+      dataIndex: 'loginLocation',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.browser" defaultMessage="浏览器类型" />,
+      dataIndex: 'browser',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.os" defaultMessage="操作系统" />,
+      dataIndex: 'os',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.status" defaultMessage="登录状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.msg" defaultMessage="提示消息" />,
+      dataIndex: 'msg',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.login_time" defaultMessage="访问时间" />,
+      dataIndex: 'loginTime',
+      valueType: 'dateTime',
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Logininfor>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="infoId"
+          key="logininforList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="clean"
+              danger
+              hidden={!access.hasPerms('monitor:logininfor:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认清空所有数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleClean();
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+            </Button>,
+            <Button
+              type="primary"
+              key="unlock"
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:unlock')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认解锁该用户的数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleUnlock(selectedRows[0].userName);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <UnlockOutlined />
+              <FormattedMessage id="monitor.logininfor.unlock" defaultMessage="解锁" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('monitor:logininfor:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getLogininforList({ ...params } as API.Monitor.LogininforListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('monitor:logininfor:remove')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+    </PageContainer>
+  );
+};
+
+export default LogininforTableList;
diff --git a/react-ui/src/pages/System/Menu/edit.tsx b/react-ui/src/pages/System/Menu/edit.tsx
new file mode 100644
index 0000000..e094255
--- /dev/null
+++ b/react-ui/src/pages/System/Menu/edit.tsx
@@ -0,0 +1,386 @@
+import React, { useEffect, useState } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTreeSelect,
+  ProFormSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { createIcon } from '@/utils/IconUtil';
+import { DictValueEnumObj } from '@/components/DictTag';
+import IconSelector from '@/components/IconSelector';
+
+export type MenuFormData = Record<string, unknown> & Partial<API.System.Menu>;
+
+export type MenuFormProps = {
+  onCancel: (flag?: boolean, formVals?: MenuFormData) => void;
+  onSubmit: (values: MenuFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Menu>;
+  visibleOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+  menuTree: DataNode[];
+};
+
+const MenuForm: React.FC<MenuFormProps> = (props) => {
+
+  const [form] = Form.useForm();
+
+  const [menuTypeId, setMenuTypeId] = useState<any>('M');
+  const [menuIconName, setMenuIconName] = useState<any>();
+  const [iconSelectorOpen, setIconSelectorOpen] = useState<boolean>(false);
+
+  const { menuTree, visibleOptions, statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    setMenuIconName(props.values.icon);
+    form.setFieldsValue({
+      menuId: props.values.menuId,
+      menuName: props.values.menuName,
+      parentId: props.values.parentId,
+      orderNum: props.values.orderNum,
+      path: props.values.path,
+      component: props.values.component,
+      query: props.values.query,
+      isFrame: props.values.isFrame,
+      isCache: props.values.isCache,
+      menuType: props.values.menuType,
+      visible: props.values.visible,
+      status: props.values.status,
+      perms: props.values.perms,
+      icon: props.values.icon,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as MenuFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.menu.title',
+        defaultMessage: '编辑菜单权限',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="menuId"
+          label={intl.formatMessage({
+            id: 'system.menu.menu_id',
+            defaultMessage: '菜单编号',
+          })}
+          placeholder="请输入菜单编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入菜单编号!" defaultMessage="请输入菜单编号!" />,
+            },
+          ]}
+        />
+        <ProFormTreeSelect
+          name="parentId"
+          label={intl.formatMessage({
+            id: 'system.menu.parent_id',
+            defaultMessage: '上级菜单',
+          })}
+          params={{menuTree}}
+          request={async () => {
+            return menuTree;
+          }}
+          placeholder="请输入父菜单编号"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入父菜单编号!" defaultMessage="请输入父菜单编号!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: 0
+          }}
+        />
+        <ProFormRadio.Group
+          name="menuType"
+          valueEnum={{
+            M: '目录',
+            C: '菜单',
+            F: '按钮',
+          }}
+          label={intl.formatMessage({
+            id: 'system.menu.menu_type',
+            defaultMessage: '菜单类型',
+          })}
+          placeholder="请输入菜单类型"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入菜单类型!" defaultMessage="请输入菜单类型!" />,
+            },
+          ]}
+          fieldProps={{
+            defaultValue: 'M',
+            onChange: (e) => {
+              setMenuTypeId(e.target.value);
+            },
+          }}
+        />
+        <ProFormSelect
+          name="icon"
+          label={intl.formatMessage({
+            id: 'system.menu.icon',
+            defaultMessage: '菜单图标',
+          })}
+          valueEnum={{}}
+          hidden={menuTypeId === 'F'}
+          addonBefore={createIcon(menuIconName)}
+          fieldProps={{
+            onClick: () => {
+              setIconSelectorOpen(true);
+            },
+          }}
+          placeholder="请输入菜单图标"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入菜单图标!" defaultMessage="请输入菜单图标!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="menuName"
+          label={intl.formatMessage({
+            id: 'system.menu.menu_name',
+            defaultMessage: '菜单名称',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入菜单名称"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入菜单名称!" defaultMessage="请输入菜单名称!" />,
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="orderNum"
+          label={intl.formatMessage({
+            id: 'system.menu.order_num',
+            defaultMessage: '显示顺序',
+          })}
+          width="lg"
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入显示顺序"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: 1
+          }}
+        />
+        <ProFormRadio.Group
+          name="isFrame"
+          valueEnum={{
+            0: '是',
+            1: '否',
+          }}
+          initialValue="1"
+          label={intl.formatMessage({
+            id: 'system.menu.is_frame',
+            defaultMessage: '是否为外链',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入是否为外链"
+          hidden={menuTypeId === 'F'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入是否为外链!" defaultMessage="请输入是否为外链!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: '1'
+          }}
+        />
+        <ProFormText
+          name="path"
+          label={intl.formatMessage({
+            id: 'system.menu.path',
+            defaultMessage: '路由地址',
+          })}
+          width="lg"
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入路由地址"
+          hidden={menuTypeId === 'F'}
+          rules={[
+            {
+              required: menuTypeId !== 'F',
+              message: <FormattedMessage id="请输入路由地址!" defaultMessage="请输入路由地址!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="component"
+          label={intl.formatMessage({
+            id: 'system.menu.component',
+            defaultMessage: '组件路径',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入组件路径"
+          hidden={menuTypeId !== 'C'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入组件路径!" defaultMessage="请输入组件路径!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="query"
+          label={intl.formatMessage({
+            id: 'system.menu.query',
+            defaultMessage: '路由参数',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入路由参数"
+          hidden={menuTypeId !== 'C'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入路由参数!" defaultMessage="请输入路由参数!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="perms"
+          label={intl.formatMessage({
+            id: 'system.menu.perms',
+            defaultMessage: '权限标识',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入权限标识"
+          hidden={menuTypeId === 'M'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入权限标识!" defaultMessage="请输入权限标识!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          name="isCache"
+          valueEnum={{
+            0: '缓存',
+            1: '不缓存',
+          }}
+          label={intl.formatMessage({
+            id: 'system.menu.is_cache',
+            defaultMessage: '是否缓存',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入是否缓存"
+          hidden={menuTypeId !== 'C'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入是否缓存!" defaultMessage="请输入是否缓存!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: 0
+          }}
+        />
+        <ProFormRadio.Group
+          name="visible"
+          valueEnum={visibleOptions}
+          label={intl.formatMessage({
+            id: 'system.menu.visible',
+            defaultMessage: '显示状态',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入显示状态"
+          hidden={menuTypeId === 'F'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入显示状态!" defaultMessage="请输入显示状态!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: '0'
+          }}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.menu.status',
+            defaultMessage: '菜单状态',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入菜单状态"
+          hidden={menuTypeId === 'F'}
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入菜单状态!" defaultMessage="请输入菜单状态!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: '0'
+          }}
+        />
+      </ProForm>
+      <Modal
+        width={800}
+        open={iconSelectorOpen}
+        onCancel={() => {
+          setIconSelectorOpen(false);
+        }}
+        footer={null}
+      >
+        <IconSelector
+          onSelect={(name: string) => {
+            form.setFieldsValue({ icon: name });
+            setMenuIconName(name);
+            setIconSelectorOpen(false);
+          }}
+        />
+      </Modal>
+    </Modal>
+  );
+};
+
+export default MenuForm;
diff --git a/react-ui/src/pages/System/Menu/index.tsx b/react-ui/src/pages/System/Menu/index.tsx
new file mode 100644
index 0000000..95ca2de
--- /dev/null
+++ b/react-ui/src/pages/System/Menu/index.tsx
@@ -0,0 +1,339 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getMenuList, removeMenu, addMenu, updateMenu } from '@/services/system/menu';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { buildTreeData } from '@/utils/tree';
+import { DataNode } from 'antd/es/tree';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Menu) => {
+  const hide = message.loading('正在添加');
+  try {
+    await addMenu({ ...fields });
+    hide();
+    message.success('添加成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Menu) => {
+  const hide = message.loading('正在配置');
+  try {
+    await updateMenu(fields);
+    hide();
+    message.success('配置成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Menu[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    await removeMenu(selectedRows.map((row) => row.menuId).join(','));
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Menu) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.menuId];
+    await removeMenu(params.join(','));
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+
+const MenuTableList: React.FC = () => {
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Menu>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Menu[]>([]);
+
+  const [menuTree, setMenuTree] = useState<DataNode[]>([]);
+  const [visibleOptions, setVisibleOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_show_hide').then((data) => {
+      setVisibleOptions(data);
+    });
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Menu>[] = [
+    {
+      title: <FormattedMessage id="system.menu.menu_name" defaultMessage="菜单名称" />,
+      dataIndex: 'menuName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.menu.icon" defaultMessage="菜单图标" />,
+      dataIndex: 'icon',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.menu.order_num" defaultMessage="显示顺序" />,
+      dataIndex: 'orderNum',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.menu.component" defaultMessage="组件路径" />,
+      dataIndex: 'component',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.menu.perms" defaultMessage="权限标识" />,
+      dataIndex: 'perms',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.menu.status" defaultMessage="菜单状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:menu:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:menu:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Menu>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          rowKey="menuId"
+          key="menuList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:menu:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:menu:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getMenuList({ ...params } as API.System.MenuListParams).then((res) => {
+              const rootMenu = { id: 0, label: '主类目', children: [] as DataNode[], value: 0 };
+              const memuData = buildTreeData(res.data, 'menuId', 'menuName', '', '', '');
+              rootMenu.children = memuData;
+              const treeData: any = [];
+              treeData.push(rootMenu);
+              setMenuTree(treeData);
+              return {
+                data: memuData,
+                total: res.data.length,
+                success: true,
+              };
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:menu:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.menuId) {
+            success = await handleUpdate({ ...values } as API.System.Menu);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Menu);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        visibleOptions={visibleOptions}
+        statusOptions={statusOptions}
+        menuTree={menuTree}
+      />
+    </PageContainer>
+  );
+};
+
+export default MenuTableList;
diff --git a/react-ui/src/pages/System/Notice/edit.tsx b/react-ui/src/pages/System/Notice/edit.tsx
new file mode 100644
index 0000000..0b111ce
--- /dev/null
+++ b/react-ui/src/pages/System/Notice/edit.tsx
@@ -0,0 +1,174 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormSelect,
+  ProFormTextArea,
+  ProFormRadio,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type NoticeFormData = Record<string, unknown> & Partial<API.System.Notice>;
+
+export type NoticeFormProps = {
+  onCancel: (flag?: boolean, formVals?: NoticeFormData) => void;
+  onSubmit: (values: NoticeFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Notice>;
+  noticeTypeOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+};
+
+const NoticeForm: React.FC<NoticeFormProps> = (props) => {
+  const [form] = Form.useForm();
+  
+  const { noticeTypeOptions,statusOptions, } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			noticeId: props.values.noticeId,
+			noticeTitle: props.values.noticeTitle,
+			noticeType: props.values.noticeType,
+			noticeContent: props.values.noticeContent,
+			status: props.values.status,
+			createBy: props.values.createBy,
+			createTime: props.values.createTime,
+			updateBy: props.values.updateBy,
+			updateTime: props.values.updateTime,
+			remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as NoticeFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.notice.title',
+        defaultMessage: '编辑通知公告',
+      })}
+      forceRender
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="noticeId"
+          label={intl.formatMessage({
+            id: 'system.notice.notice_id',
+            defaultMessage: '公告编号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入公告编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入公告编号!" defaultMessage="请输入公告编号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="noticeTitle"
+          label={intl.formatMessage({
+            id: 'system.notice.notice_title',
+            defaultMessage: '公告标题',
+          })}
+          placeholder="请输入公告标题"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入公告标题!" defaultMessage="请输入公告标题!" />,                  
+            },
+          ]}
+        />
+        <ProFormSelect
+          valueEnum={noticeTypeOptions}
+          name="noticeType"
+          label={intl.formatMessage({
+            id: 'system.notice.notice_type',
+            defaultMessage: '公告类型',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入公告类型"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入公告类型!" defaultMessage="请输入公告类型!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.notice.status',
+            defaultMessage: '公告状态',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入公告状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入公告状态!" defaultMessage="请输入公告状态!" />,                  
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="noticeContent"
+          label={intl.formatMessage({
+            id: 'system.notice.notice_content',
+            defaultMessage: '公告内容',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入公告内容"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入公告内容!" defaultMessage="请输入公告内容!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.notice.remark',
+            defaultMessage: '备注',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default NoticeForm;
diff --git a/react-ui/src/pages/System/Notice/index.tsx b/react-ui/src/pages/System/Notice/index.tsx
new file mode 100644
index 0000000..ea5b063
--- /dev/null
+++ b/react-ui/src/pages/System/Notice/index.tsx
@@ -0,0 +1,366 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getNoticeList, removeNotice, addNotice, updateNotice } from '@/services/system/notice';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Notice) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addNotice({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Notice) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateNotice(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Notice[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeNotice(selectedRows.map((row) => row.noticeId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Notice) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.noticeId];
+    const resp = await removeNotice(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+
+
+const NoticeTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Notice>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Notice[]>([]);
+
+  const [noticeTypeOptions, setNoticeTypeOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_notice_type').then((data) => {
+      setNoticeTypeOptions(data);
+    });
+    getDictValueEnum('sys_notice_status').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Notice>[] = [
+    {
+      title: <FormattedMessage id="system.notice.notice_id" defaultMessage="公告编号" />,
+      dataIndex: 'noticeId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.notice.notice_title" defaultMessage="公告标题" />,
+      dataIndex: 'noticeTitle',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.notice.notice_type" defaultMessage="公告类型" />,
+      dataIndex: 'noticeType',
+      valueType: 'select',
+      valueEnum: noticeTypeOptions,
+    },
+    {
+      title: <FormattedMessage id="system.notice.notice_content" defaultMessage="公告内容" />,
+      dataIndex: 'noticeContent',
+      valueType: 'text',
+      hideInTable: true,
+    },
+    {
+      title: <FormattedMessage id="system.notice.status" defaultMessage="公告状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.notice.remark" defaultMessage="备注" />,
+      dataIndex: 'remark',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.notice.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:notice:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:notice:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Notice>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="noticeId"
+          key="noticeList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:notice:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:notice:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getNoticeList({ ...params } as API.System.NoticeListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:notice:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.noticeId) {
+            success = await handleUpdate({ ...values } as API.System.Notice);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Notice);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        noticeTypeOptions={noticeTypeOptions}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default NoticeTableList;
diff --git a/react-ui/src/pages/System/Operlog/detail.tsx b/react-ui/src/pages/System/Operlog/detail.tsx
new file mode 100644
index 0000000..f5b4f65
--- /dev/null
+++ b/react-ui/src/pages/System/Operlog/detail.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { Descriptions, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { getValueEnumLabel } from '@/utils/options';
+
+export type OperlogFormData = Record<string, unknown> & Partial<API.Monitor.Operlog>;
+
+export type OperlogFormProps = {
+  onCancel: (flag?: boolean, formVals?: OperlogFormData) => void;
+  onSubmit: (values: OperlogFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Operlog>;
+  businessTypeOptions: DictValueEnumObj;
+  operatorTypeOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+};
+
+const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => {
+
+  const { values, businessTypeOptions, operatorTypeOptions, statusOptions, } = props;
+
+  const intl = useIntl();
+  const handleOk = () => {
+    console.log("handle ok");
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'monitor.operlog.title',
+        defaultMessage: '编辑操作日志记录',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Descriptions column={24}>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.module" defaultMessage="操作模块" />}
+        >
+          {`${values.title}/${getValueEnumLabel(businessTypeOptions, values.businessType)}`}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />}
+        >
+          {values.requestMethod}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />}
+        >
+          {`${values.operName}/${values.operIp}`}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />}
+        >
+          {getValueEnumLabel(operatorTypeOptions, values.operatorType)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.method" defaultMessage="方法名称" />}
+        >
+          {values.method}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.oper_url" defaultMessage="请求URL" />}
+        >
+          {values.operUrl}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.oper_param" defaultMessage="请求参数" />}
+        >
+          {values.operParam}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.json_result" defaultMessage="返回参数" />}
+        >
+          {values.jsonResult}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.error_msg" defaultMessage="错误消息" />}
+        >
+          {values.errorMsg}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />}
+        >
+          {getValueEnumLabel(statusOptions, values.status)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />}
+        >
+          {values.operTime?.toString()}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default OperlogDetailForm;
diff --git a/react-ui/src/pages/System/Operlog/index.tsx b/react-ui/src/pages/System/Operlog/index.tsx
new file mode 100644
index 0000000..be46709
--- /dev/null
+++ b/react-ui/src/pages/System/Operlog/index.tsx
@@ -0,0 +1,411 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getOperlogList, removeOperlog, addOperlog, updateOperlog, cleanAllOperlog, exportOperlog } from '@/services/monitor/operlog';
+import UpdateForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Operlog) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addOperlog({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Operlog) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateOperlog(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Operlog[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeOperlog(selectedRows.map((row) => row.operId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 清空所有记录
+ *
+ */
+const handleCleanAll = async () => {
+  const hide = message.loading('正在清空');
+  try {
+    const resp = await cleanAllOperlog();
+    hide();
+    if (resp.code === 200) {
+      message.success('清空成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('清空失败,请重试');
+    return false;
+  }
+};
+
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportOperlog();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const OperlogTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.Monitor.Operlog>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Operlog[]>([]);
+
+  const [businessTypeOptions, setBusinessTypeOptions] = useState<any>([]);
+  const [operatorTypeOptions, setOperatorTypeOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_oper_type', true).then((data) => {
+      setBusinessTypeOptions(data);
+    });
+    getDictValueEnum('sys_oper_type', true).then((data) => {
+      setOperatorTypeOptions(data);
+    });
+    getDictValueEnum('sys_common_status', true).then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Operlog>[] = [
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_id" defaultMessage="日志主键" />,
+      dataIndex: 'operId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.title" defaultMessage="操作模块" />,
+      dataIndex: 'title',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.business_type" defaultMessage="业务类型" />,
+      dataIndex: 'businessType',
+      valueType: 'select',
+      valueEnum: businessTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={businessTypeOptions} value={record.businessType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />,
+      dataIndex: 'requestMethod',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />,
+      dataIndex: 'operatorType',
+      valueType: 'select',
+      valueEnum: operatorTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={operatorTypeOptions} value={record.operatorType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />,
+      dataIndex: 'operName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_ip" defaultMessage="主机地址" />,
+      dataIndex: 'operIp',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_location" defaultMessage="操作地点" />,
+      dataIndex: 'operLocation',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag key="status" enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />,
+      dataIndex: 'operTime',
+      valueType: 'dateTime',
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:operlog:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          详细
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Operlog>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="operId"
+          key="operlogList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:operlog:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:operlog:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="clean"
+              danger
+              hidden={!access.hasPerms('system:operlog:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认清空所有数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleCleanAll();
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:operlog:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getOperlogList({ ...params } as API.Monitor.OperlogListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:operlog:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.operId) {
+            success = await handleUpdate({ ...values } as API.Monitor.Operlog);
+          } else {
+            success = await handleAdd({ ...values } as API.Monitor.Operlog);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        businessTypeOptions={businessTypeOptions}
+        operatorTypeOptions={operatorTypeOptions}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default OperlogTableList;
diff --git a/react-ui/src/pages/System/Post/edit.tsx b/react-ui/src/pages/System/Post/edit.tsx
new file mode 100644
index 0000000..555fcde
--- /dev/null
+++ b/react-ui/src/pages/System/Post/edit.tsx
@@ -0,0 +1,166 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTextArea,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type PostFormData = Record<string, unknown> & Partial<API.System.Post>;
+
+export type PostFormProps = {
+  onCancel: (flag?: boolean, formVals?: PostFormData) => void;
+  onSubmit: (values: PostFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Post>;
+  statusOptions: DictValueEnumObj;
+};
+
+const PostForm: React.FC<PostFormProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const { statusOptions, } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			postId: props.values.postId,
+			postCode: props.values.postCode,
+			postName: props.values.postName,
+			postSort: props.values.postSort,
+			status: props.values.status,
+			createBy: props.values.createBy,
+			createTime: props.values.createTime,
+			updateBy: props.values.updateBy,
+			updateTime: props.values.updateTime,
+			remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as PostFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.post.title',
+        defaultMessage: '编辑岗位信息',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="postId"
+          label={intl.formatMessage({
+            id: 'system.post.post_id',
+            defaultMessage: '岗位编号',
+          })}
+          placeholder="请输入岗位编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入岗位编号!" defaultMessage="请输入岗位编号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="postName"
+          label={intl.formatMessage({
+            id: 'system.post.post_name',
+            defaultMessage: '岗位名称',
+          })}
+          placeholder="请输入岗位名称"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入岗位名称!" defaultMessage="请输入岗位名称!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="postCode"
+          label={intl.formatMessage({
+            id: 'system.post.post_code',
+            defaultMessage: '岗位编码',
+          })}
+          placeholder="请输入岗位编码"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入岗位编码!" defaultMessage="请输入岗位编码!" />,                  
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="postSort"
+          label={intl.formatMessage({
+            id: 'system.post.post_sort',
+            defaultMessage: '显示顺序',
+          })}
+          placeholder="请输入显示顺序"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.post.status',
+            defaultMessage: '状态',
+          })}
+          placeholder="请输入状态"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,                  
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.post.remark',
+            defaultMessage: '备注',
+          })}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default PostForm;
diff --git a/react-ui/src/pages/System/Post/index.tsx b/react-ui/src/pages/System/Post/index.tsx
new file mode 100644
index 0000000..ad98aae
--- /dev/null
+++ b/react-ui/src/pages/System/Post/index.tsx
@@ -0,0 +1,366 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getPostList, removePost, addPost, updatePost, exportPost } from '@/services/system/post';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Post) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addPost({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Post) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updatePost(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Post[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removePost(selectedRows.map((row) => row.postId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Post) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.postId];
+    const resp = await removePost(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportPost();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const PostTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Post>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Post[]>([]);
+
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Post>[] = [
+    {
+      title: <FormattedMessage id="system.post.post_id" defaultMessage="岗位编号" />,
+      dataIndex: 'postId',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.post.post_code" defaultMessage="岗位编码" />,
+      dataIndex: 'postCode',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.post.post_name" defaultMessage="岗位名称" />,
+      dataIndex: 'postName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.post.post_sort" defaultMessage="显示顺序" />,
+      dataIndex: 'postSort',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.post.status" defaultMessage="状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:post:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:post:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Post>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="postId"
+          key="postList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:post:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:post:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:post:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getPostList({ ...params } as API.System.PostListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:post:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.postId) {
+            success = await handleUpdate({ ...values } as API.System.Post);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Post);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default PostTableList;
diff --git a/react-ui/src/pages/System/Role/authUser.tsx b/react-ui/src/pages/System/Role/authUser.tsx
new file mode 100644
index 0000000..c553d99
--- /dev/null
+++ b/react-ui/src/pages/System/Role/authUser.tsx
@@ -0,0 +1,274 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history, useParams } from '@umijs/max';
+import { Button, Modal, message } from 'antd';
+import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, RollbackOutlined } from '@ant-design/icons';
+import { authUserSelectAll, authUserCancel, authUserCancelAll, allocatedUserList, unallocatedUserList } from '@/services/system/role';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+import UserSelectorModal from './components/UserSelectorModal';
+import { HttpResult } from '@/enums/httpEnum';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const cancelAuthUserAll = async (roleId: string, selectedRows: API.System.User[]) => {
+	const hide = message.loading('正在取消授权');
+	if (!selectedRows) return true;
+	try {
+		const userIds = selectedRows.map((row) => row.userId).join(',');
+		const resp = await authUserCancelAll({roleId, userIds});
+		hide();
+		if (resp.code === 200) {
+			message.success('取消授权成功,即将刷新');
+		} else {
+			message.error(resp.msg);
+		}
+		return true;
+	} catch (error) {
+		hide();
+		message.error('取消授权失败,请重试');
+		return false;
+	}
+};
+
+const cancelAuthUser = async (roleId: string, userId: number) => {
+	const hide = message.loading('正在取消授权');
+	try {
+		const resp = await authUserCancel({ userId, roleId });
+		hide();
+		if (resp.code === 200) {
+			message.success('取消授权成功,即将刷新');
+		} else {
+			message.error(resp.msg);
+		}
+		return true;
+	} catch (error) {
+		hide();
+		message.error('取消授权失败,请重试');
+		return false;
+	}
+};
+
+
+const AuthUserTableList: React.FC = () => {
+
+	const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+	const actionRef = useRef<ActionType>();
+	const [selectedRows, setSelectedRows] = useState<API.System.User[]>([]);
+	const [statusOptions, setStatusOptions] = useState<any>([]);
+
+	const access = useAccess();
+
+	/** 国际化配置 */
+	const intl = useIntl();
+
+	const params = useParams();
+	if (params.id === undefined) {
+		history.back();
+	}
+	const roleId = params.id || '0';
+
+	useEffect(() => {
+		getDictValueEnum('sys_normal_disable').then((data) => {
+			setStatusOptions(data);
+		});
+	}, []);
+
+	const columns: ProColumns<API.System.User>[] = [
+		{
+			title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+			dataIndex: 'deptId',
+			valueType: 'text',
+		},
+		{
+			title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+			dataIndex: 'userName',
+			valueType: 'text',
+		},
+		{
+			title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+			dataIndex: 'nickName',
+			valueType: 'text',
+		},
+		{
+			title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+			dataIndex: 'phonenumber',
+			valueType: 'text',
+		},
+		{
+			title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+			dataIndex: 'createTime',
+			valueType: 'dateRange',
+			render: (_, record) => {
+				return (<span>{record.createTime.toString()} </span>);
+			},
+			hideInSearch: true,
+		},
+		{
+			title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+			dataIndex: 'status',
+			valueType: 'select',
+			valueEnum: statusOptions,
+			render: (_, record) => {
+				return (<DictTag enums={statusOptions} value={record.status} />);
+			},
+		},
+		{
+			title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+			dataIndex: 'option',
+			width: '60px',
+			valueType: 'option',
+			render: (_, record) => [
+				<Button
+					type="link"
+					size="small"
+					danger
+					icon={<DeleteOutlined />}
+					key="remove"
+					hidden={!access.hasPerms('system:role:remove')}
+					onClick={async () => {
+						Modal.confirm({
+							title: '删除',
+							content: '确认要取消该用户' + record.userName + '"角色授权吗?',
+							okText: '确认',
+							cancelText: '取消',
+							onOk: async () => {
+								const success = await cancelAuthUser(roleId, record.userId);
+								if (success) {
+									if (actionRef.current) {
+										actionRef.current.reload();
+									}
+								}
+							},
+						});
+					}}
+				>
+					取消授权
+				</Button>,
+			],
+		},
+	];
+
+	return (
+		<PageContainer>
+			<div style={{ width: '100%', float: 'right' }}>
+				<ProTable<API.System.User>
+					headerTitle={intl.formatMessage({
+						id: 'pages.searchTable.title',
+						defaultMessage: '信息',
+					})}
+					actionRef={actionRef}
+					rowKey="userId"
+					key="userList"
+					search={{
+						labelWidth: 120,
+					}}
+					toolBarRender={() => [
+						<Button
+							type="primary"
+							key="add"
+							hidden={!access.hasPerms('system:role:add')}
+							onClick={async () => {
+								setModalVisible(true);
+							}}
+						>
+							<PlusOutlined /> <FormattedMessage id="system.role.auth.addUser" defaultMessage="添加用户" />
+						</Button>,
+						<Button
+							type="primary"
+							key="remove"
+              danger
+							hidden={selectedRows?.length === 0 || !access.hasPerms('system:role:remove')}
+							onClick={async () => {
+								Modal.confirm({
+									title: '是否确认删除所选数据项?',
+									icon: <ExclamationCircleOutlined />,
+									content: '请谨慎操作',
+									async onOk() {
+										const success = await cancelAuthUserAll(roleId, selectedRows);
+										if (success) {
+											setSelectedRows([]);
+											actionRef.current?.reloadAndRest?.();
+										}
+									},
+									onCancel() { },
+								});
+							}}
+						>
+							<DeleteOutlined />
+							<FormattedMessage id="system.role.auth.cancelAll" defaultMessage="批量取消授权" />
+						</Button>,
+						<Button
+							type="primary"
+							key="back"
+							onClick={async () => {
+								history.back();
+							}}
+						>
+							<RollbackOutlined />
+							<FormattedMessage id="pages.goback" defaultMessage="返回" />
+						</Button>,
+					]}
+					request={(params) =>
+						allocatedUserList({ ...params, roleId } as API.System.RoleListParams).then((res) => {
+							const result = {
+								data: res.rows,
+								total: res.total,
+								success: true,
+							};
+							return result;
+						})
+					}
+					columns={columns}
+					rowSelection={{
+						onChange: (_, selectedRows) => {
+							setSelectedRows(selectedRows);
+						},
+					}}
+				/>
+			</div>
+			<UserSelectorModal
+				open={modalVisible}
+				onSubmit={(values: React.Key[]) => {
+					const userIds = values.join(",");
+					if (userIds === "") {
+						message.warning("请选择要分配的用户");
+						return;
+					}
+					authUserSelectAll({ roleId: roleId, userIds: userIds }).then(resp => {
+						if (resp.code === HttpResult.SUCCESS) {
+							message.success('更新成功!');
+							if (actionRef.current) {
+								actionRef.current.reload();
+							}
+						} else {
+							message.warning(resp.msg);
+						}
+					})
+					setModalVisible(false);
+				}}
+				onCancel={() => {
+					setModalVisible(false);
+				}}
+				params={{roleId}}
+				request={(params) =>
+					unallocatedUserList({ ...params } as API.System.RoleListParams).then((res) => {
+						const result = {
+							data: res.rows,
+							total: res.rows.length,
+							success: true,
+						};
+						return result;
+					})
+				}
+			/>
+		</PageContainer>
+	);
+};
+
+export default AuthUserTableList;
diff --git a/react-ui/src/pages/System/Role/components/DataScope.tsx b/react-ui/src/pages/System/Role/components/DataScope.tsx
new file mode 100644
index 0000000..a3dcce0
--- /dev/null
+++ b/react-ui/src/pages/System/Role/components/DataScope.tsx
@@ -0,0 +1,233 @@
+import React, { useEffect, useState } from 'react';
+import { Checkbox, Col, Form, Modal, Row, Tree } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { Key, ProForm, ProFormDigit, ProFormSelect, ProFormText } from '@ant-design/pro-components';
+import { DataNode } from 'antd/es/tree';
+import { CheckboxValueType } from 'antd/es/checkbox/Group';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+export type FormValueType = any & Partial<API.System.Dept>;
+
+export type DataScopeFormProps = {
+    onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+    onSubmit: (values: FormValueType) => Promise<void>;
+    open: boolean;
+    values: Partial<API.System.Role>;
+    deptTree: DataNode[];
+    deptCheckedKeys: string[];
+};
+
+const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
+    const [form] = Form.useForm();
+
+    const { deptTree, deptCheckedKeys } = props;
+    const [dataScopeType, setDataScopeType] = useState<string | undefined>('1');
+    const [deptIds, setDeptIds] = useState<string[] | {checked: string[], halfChecked: string[]}>([]);
+    const [deptTreeExpandKey, setDeptTreeExpandKey] = useState<Key[]>([]);
+    const [checkStrictly, setCheckStrictly] = useState<boolean>(true);
+
+
+    useEffect(() => {
+        setDeptIds(deptCheckedKeys);
+        form.resetFields();
+        form.setFieldsValue({
+            roleId: props.values.roleId,
+            roleName: props.values.roleName,
+            roleKey: props.values.roleKey,
+            dataScope: props.values.dataScope,
+        });
+        setDataScopeType(props.values.dataScope);
+    }, [props.values]);
+
+    const intl = useIntl();
+    const handleOk = () => {
+        form.submit();
+    };
+    const handleCancel = () => {
+        props.onCancel();
+    };
+    const handleFinish = async (values: Record<string, any>) => {
+        props.onSubmit({ ...values, deptIds } as FormValueType);
+    };
+
+    const getAllDeptNode = (node: DataNode[]) => {
+        let keys: any[] = [];
+        node.forEach(value => {
+            keys.push(value.key);
+            if(value.children) {
+                keys = keys.concat(getAllDeptNode(value.children));
+            }
+        });
+        return keys;
+    }
+
+    const deptAllNodes = getAllDeptNode(deptTree);
+
+
+    const onDeptOptionChange = (checkedValues: CheckboxValueType[]) => {
+        if(checkedValues.includes('deptExpand')) {
+            setDeptTreeExpandKey(deptAllNodes);
+        } else {
+            setDeptTreeExpandKey([]);
+        }
+        if(checkedValues.includes('deptNodeAll')) {
+            setDeptIds(deptAllNodes);
+        } else {
+            setDeptIds([]);
+        }
+        
+        if(checkedValues.includes('deptCheckStrictly')) {
+            setCheckStrictly(false);
+        } else {
+            setCheckStrictly(true);
+        }
+    };
+
+    return (
+        <Modal
+            width={640}
+            title={intl.formatMessage({
+                id: 'system.user.auth.role',
+                defaultMessage: '分配角色',
+            })}
+            open={props.open}
+            destroyOnClose
+            forceRender
+            onOk={handleOk}
+            onCancel={handleCancel}
+        >
+            <ProForm
+                form={form}
+                grid={true}
+                layout="horizontal"
+                onFinish={handleFinish}
+                initialValues={{
+                    login_password: '',
+                    confirm_password: '',
+                }}
+            >
+
+                <ProFormDigit
+                    name="roleId"
+                    label={intl.formatMessage({
+                        id: 'system.role.role_id',
+                        defaultMessage: '角色编号',
+                    })}
+                    colProps={{ md: 12, xl: 12 }}
+                    placeholder="请输入角色编号"
+                    disabled
+                    hidden={true}
+                    rules={[
+                        {
+                            required: false,
+                            message: <FormattedMessage id="请输入角色编号!" defaultMessage="请输入角色编号!" />,
+                        },
+                    ]}
+                />
+                <ProFormText
+                    name="roleName"
+                    label={intl.formatMessage({
+                        id: 'system.role.role_name',
+                        defaultMessage: '角色名称',
+                    })}
+                    disabled
+                    placeholder="请输入角色名称"
+                    rules={[
+                        {
+                            required: true,
+                            message: <FormattedMessage id="请输入角色名称!" defaultMessage="请输入角色名称!" />,
+                        },
+                    ]}
+                />
+                <ProFormText
+                    name="roleKey"
+                    label={intl.formatMessage({
+                        id: 'system.role.role_key',
+                        defaultMessage: '权限字符串',
+                    })}
+                    disabled
+                    placeholder="请输入角色权限字符串"
+                    rules={[
+                        {
+                            required: true,
+                            message: <FormattedMessage id="请输入角色权限字符串!" defaultMessage="请输入角色权限字符串!" />,
+                        },
+                    ]}
+                />
+                <ProFormSelect
+                    name="dataScope"
+                    label='权限范围'
+                    initialValue={'1'}
+                    placeholder="请输入用户性别"
+                    valueEnum={{
+                        "1": "全部数据权限",
+                        "2": "自定数据权限",
+                        "3": "本部门数据权限",
+                        "4": "本部门及以下数据权限",
+                        "5": "仅本人数据权限"
+                    }}
+                    rules={[
+                        {
+                            required: true,
+                        },
+                    ]}
+                    fieldProps={{
+                        onChange: (value) => {
+                            setDataScopeType(value);
+                        },
+                    }}
+                />
+                <ProForm.Item
+                    name="deptIds"
+                    label={intl.formatMessage({
+                        id: 'system.role.auth',
+                        defaultMessage: '菜单权限',
+                    })}
+                    required={dataScopeType === '1'}
+                    hidden={dataScopeType !== '1'}
+                >
+                    <Row gutter={[16, 16]}>
+                        <Col md={24}>
+                            <Checkbox.Group
+                                options={[
+                                    { label: '展开/折叠', value: 'deptExpand' },
+                                    { label: '全选/全不选', value: 'deptNodeAll' },
+                                    // { label: '父子联动', value: 'deptCheckStrictly' },
+                                ]}
+                                onChange={onDeptOptionChange} />
+                        </Col>
+                        <Col md={24}>
+                            <Tree
+                                checkable={true}
+                                checkStrictly={checkStrictly}
+                                expandedKeys={deptTreeExpandKey}
+                                treeData={deptTree}
+                                checkedKeys={deptIds}
+                                defaultCheckedKeys={deptCheckedKeys}
+                                onCheck={(checkedKeys: any, checkInfo: any) => {
+                                    console.log(checkedKeys, checkInfo);
+                                    if(checkStrictly) {
+                                        return setDeptIds(checkedKeys.checked);
+                                    } else {
+                                        return setDeptIds({checked: checkedKeys, halfChecked: checkInfo.halfCheckedKeys});
+                                    }
+                                }}
+                                onExpand={(expandedKeys: Key[]) => {
+                                    setDeptTreeExpandKey(deptTreeExpandKey.concat(expandedKeys));
+                                }}
+                            />
+                        </Col>
+                    </Row>
+                </ProForm.Item>
+            </ProForm>
+        </Modal>
+    );
+};
+
+export default DataScopeForm;
diff --git a/react-ui/src/pages/System/Role/components/UserSelectorModal.tsx b/react-ui/src/pages/System/Role/components/UserSelectorModal.tsx
new file mode 100644
index 0000000..fdcfb85
--- /dev/null
+++ b/react-ui/src/pages/System/Role/components/UserSelectorModal.tsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Modal } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { ActionType, ParamsType, ProColumns, ProTable, RequestData } from '@ant-design/pro-components';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/10
+ * 
+ * */
+
+export type DataScopeFormProps = {
+  onCancel: () => void;
+  onSubmit: (values: React.Key[]) => void;
+  open: boolean;
+  params: ParamsType;
+  request?: (params: Record<string, any>) => Promise<Partial<RequestData<API.System.User>>>;
+};
+
+const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
+  
+  const actionRef = useRef<ActionType>();
+  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, [props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    props.onSubmit(selectedRowKeys);
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+
+  const columns: ProColumns<API.System.User>[] = [
+    {
+      title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+      dataIndex: 'userId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+      dataIndex: 'nickName',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+      dataIndex: 'phonenumber',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      hideInSearch: true,
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.user.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      hideInSearch: true,
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+    }
+  ];
+
+  return (
+    <Modal
+      width={800}
+      title={intl.formatMessage({
+        id: 'system.role.auth.user',
+        defaultMessage: '选择用户',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProTable<API.System.User>
+        headerTitle={intl.formatMessage({
+          id: 'pages.searchTable.title',
+          defaultMessage: '信息',
+        })}
+        actionRef={actionRef}
+        rowKey="userId"
+        key="userList"
+        search={{
+          labelWidth: 120,
+        }}
+        toolbar={{}}
+        params={props.params}
+        request={props.request}
+        columns={columns}
+        rowSelection={{
+          onChange: (selectedRowKeys: React.Key[]) => {
+            setSelectedRowKeys(selectedRowKeys);
+          },
+        }}
+      />
+    </Modal>
+  );
+};
+
+export default UserSelectorModal;
diff --git a/react-ui/src/pages/System/Role/edit.tsx b/react-ui/src/pages/System/Role/edit.tsx
new file mode 100644
index 0000000..580493a
--- /dev/null
+++ b/react-ui/src/pages/System/Role/edit.tsx
@@ -0,0 +1,199 @@
+import React, { useEffect, useState } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTextArea,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import Tree, { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type RoleFormData = Record<string, unknown> & Partial<API.System.Role>;
+
+export type RoleFormProps = {
+  onCancel: (flag?: boolean, formVals?: RoleFormData) => void;
+  onSubmit: (values: RoleFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Role>;
+  menuTree: DataNode[];
+  menuCheckedKeys: string[];
+  statusOptions: DictValueEnumObj;
+};
+
+const RoleForm: React.FC<RoleFormProps> = (props) => {
+  const [form] = Form.useForm();
+  const { menuTree, menuCheckedKeys } = props;
+  const [menuIds, setMenuIds] = useState<string[]>([]);
+  const { statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      roleId: props.values.roleId,
+      roleName: props.values.roleName,
+      roleKey: props.values.roleKey,
+      roleSort: props.values.roleSort,
+      dataScope: props.values.dataScope,
+      menuCheckStrictly: props.values.menuCheckStrictly,
+      deptCheckStrictly: props.values.deptCheckStrictly,
+      status: props.values.status,
+      delFlag: props.values.delFlag,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit({ ...values, menuIds } as RoleFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.role.title',
+        defaultMessage: '编辑角色信息',
+      })}
+      forceRender
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        layout="horizontal"
+        submitter={false}
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="roleId"
+          label={intl.formatMessage({
+            id: 'system.role.role_id',
+            defaultMessage: '角色编号',
+          })}
+          placeholder="请输入角色编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入角色编号!" defaultMessage="请输入角色编号!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="roleName"
+          label={intl.formatMessage({
+            id: 'system.role.role_name',
+            defaultMessage: '角色名称',
+          })}
+          placeholder="请输入角色名称"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入角色名称!" defaultMessage="请输入角色名称!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="roleKey"
+          label={intl.formatMessage({
+            id: 'system.role.role_key',
+            defaultMessage: '权限字符串',
+          })}
+          placeholder="请输入角色权限字符串"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入角色权限字符串!" defaultMessage="请输入角色权限字符串!" />,
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="roleSort"
+          label={intl.formatMessage({
+            id: 'system.role.role_sort',
+            defaultMessage: '显示顺序',
+          })}
+          placeholder="请输入显示顺序"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: 1
+          }}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.role.status',
+            defaultMessage: '角色状态',
+          })}
+          placeholder="请输入角色状态"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入角色状态!" defaultMessage="请输入角色状态!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: "0"
+          }}
+        />
+        <ProForm.Item
+          name="menuIds"
+          label={intl.formatMessage({
+            id: 'system.role.auth',
+            defaultMessage: '菜单权限',
+          })}
+        >
+          <Tree
+            checkable={true}
+            multiple={true}
+            checkStrictly={true}
+            defaultExpandAll={false}
+            treeData={menuTree}
+            defaultCheckedKeys={menuCheckedKeys}
+            onCheck={(checkedKeys: any) => {             
+              return setMenuIds(checkedKeys.checked);
+            }}
+          />
+        </ProForm.Item>
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.role.remark',
+            defaultMessage: '备注',
+          })}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default RoleForm;
diff --git a/react-ui/src/pages/System/Role/index.tsx b/react-ui/src/pages/System/Role/index.tsx
new file mode 100644
index 0000000..ea15b2e
--- /dev/null
+++ b/react-ui/src/pages/System/Role/index.tsx
@@ -0,0 +1,513 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { Button, message, Modal, Dropdown, FormInstance, Space, Switch } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined } from '@ant-design/icons';
+import { getRoleList, removeRole, addRole, updateRole, exportRole, getRoleMenuList, changeRoleStatus, updateRoleDataScope, getDeptTreeSelect, getRole } from '@/services/system/role';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { formatTreeData } from '@/utils/tree';
+import { getMenuTree } from '@/services/system/menu';
+import DataScopeForm from './components/DataScope';
+
+const { confirm } = Modal;
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Role) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addRole({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Role) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateRole(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Role[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeRole(selectedRows.map((row) => row.roleId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Role) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.roleId];
+    const resp = await removeRole(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportRole();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const RoleTableList: React.FC = () => {
+
+  const [messageApi, contextHolder] = message.useMessage();
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+  const [dataScopeModalOpen, setDataScopeModalOpen] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Role>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Role[]>([]);
+
+  const [menuTree, setMenuTree] = useState<DataNode[]>();
+  const [menuIds, setMenuIds] = useState<string[]>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const showChangeStatusConfirm = (record: API.System.Role) => {
+    let text = record.status === "1" ? "启用" : "停用";
+    const newStatus = record.status === '0' ? '1' : '0';
+    confirm({
+      title: `确认要${text}${record.roleName}角色吗?`,
+      onOk() {
+        changeRoleStatus(record.roleId, newStatus).then(resp => {
+          if (resp.code === 200) {
+            messageApi.open({
+              type: 'success',
+              content: '更新成功!',
+            });
+            actionRef.current?.reload();
+          } else {
+            messageApi.open({
+              type: 'error',
+              content: '更新失败!',
+            });
+          }
+        });
+      },
+    });
+  };
+
+  const columns: ProColumns<API.System.Role>[] = [
+    {
+      title: <FormattedMessage id="system.role.role_id" defaultMessage="角色编号" />,
+      dataIndex: 'roleId',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.role.role_name" defaultMessage="角色名称" />,
+      dataIndex: 'roleName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.role.role_key" defaultMessage="角色权限字符串" />,
+      dataIndex: 'roleKey',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.role.role_sort" defaultMessage="显示顺序" />,
+      dataIndex: 'roleSort',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.role.status" defaultMessage="角色状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (
+          <Switch
+            checked={record.status === '0'}
+            checkedChildren="正常"
+            unCheckedChildren="停用"
+            defaultChecked
+            onClick={() => showChangeStatusConfirm(record)}
+          />)
+      },
+    },
+    {
+      title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          icon=<EditOutlined />
+          hidden={!access.hasPerms('system:role:edit')}
+          onClick={() => {
+            getRoleMenuList(record.roleId).then((res) => {
+              if (res.code === 200) {
+                const treeData = formatTreeData(res.menus);
+                setMenuTree(treeData);
+                setMenuIds(res.checkedKeys.map(item => {
+                  return `${item}`
+                }));
+                setModalVisible(true);
+                setCurrentRow(record);
+              } else {
+                message.warning(res.msg);
+              }
+            });
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          icon=<DeleteOutlined />
+          hidden={!access.hasPerms('system:role:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+        <Dropdown
+          key="more"
+          menu={{
+            items: [
+              {
+                label: '数据权限',
+                key: 'datascope',
+                disabled: !access.hasPerms('system:role:edit'),
+              },
+              {
+                label: '分配用户',
+                key: 'authUser',
+                disabled: !access.hasPerms('system:role:edit'),
+              },
+            ],
+            onClick: ({ key }: any) => {
+              if (key === 'datascope') {
+                getRole(record.roleId).then(resp => {
+                  if(resp.code === 200) {
+                    setCurrentRow(resp.data);
+                    setDataScopeModalOpen(true);
+                  }
+                })
+                getDeptTreeSelect(record.roleId).then(resp => {
+                  if (resp.code === 200) {
+                    setMenuTree(formatTreeData(resp.depts));
+                    setMenuIds(resp.checkedKeys.map((item:number) => {
+                      return `${item}`
+                    }));
+                  }
+                })
+              }
+              else if (key === 'authUser') {
+                history.push(`/system/role-auth/user/${record.roleId}`);
+              }
+            }
+          }}
+        >
+          <a onClick={(e) => e.preventDefault()}>
+            <Space>
+              <DownOutlined />
+              更多
+            </Space>
+          </a>
+        </Dropdown>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      {contextHolder}
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Role>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="roleId"
+          key="roleList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:role:add')}
+              onClick={async () => {
+                getMenuTree().then((res: any) => {
+                  if (res.code === 200) {
+                    const treeData = formatTreeData(res.data);
+                    setMenuTree(treeData);
+                    setMenuIds([]);
+                    setModalVisible(true);
+                    setCurrentRow(undefined);
+                  } else {
+                    message.warning(res.msg);
+                  }
+                });
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:role:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:role:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getRoleList({ ...params } as API.System.RoleListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:role:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.roleId) {
+            success = await handleUpdate({ ...values } as API.System.Role);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Role);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        menuTree={menuTree || []}
+        menuCheckedKeys={menuIds || []}
+        statusOptions={statusOptions}
+      />
+      <DataScopeForm
+        onSubmit={async (values: any) => {
+          const success = await updateRoleDataScope(values);
+          if (success) {
+            setDataScopeModalOpen(false);
+            setSelectedRows([]);
+            setCurrentRow(undefined);
+            message.success('配置成功。');
+          }
+        }}
+        onCancel={() => {
+          setDataScopeModalOpen(false);
+          setSelectedRows([]);
+          setCurrentRow(undefined);
+        }}
+        open={dataScopeModalOpen}
+        values={currentRow || {}}
+        deptTree={menuTree || []}
+        deptCheckedKeys={menuIds || []}
+      />
+    </PageContainer>
+  );
+};
+
+export default RoleTableList;
diff --git a/react-ui/src/pages/System/User/components/AuthRole.tsx b/react-ui/src/pages/System/User/components/AuthRole.tsx
new file mode 100644
index 0000000..120d966
--- /dev/null
+++ b/react-ui/src/pages/System/User/components/AuthRole.tsx
@@ -0,0 +1,81 @@
+import React, { useEffect } from 'react';
+import { Form, Modal } from 'antd';
+import { useIntl } from '@umijs/max';
+import { ProForm, ProFormSelect } from '@ant-design/pro-components';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+export type FormValueType = any & Partial<API.System.Dept>;
+
+export type AuthRoleFormProps = {
+    onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+    onSubmit: (values: FormValueType) => Promise<void>;
+    open: boolean;
+    roleIds: number[];
+    roles: string[];
+};
+
+const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => {
+    const [form] = Form.useForm();
+
+    useEffect(() => {
+        form.resetFields();
+        form.setFieldValue( 'roleIds', props.roleIds);
+    });
+
+    const intl = useIntl();
+    const handleOk = () => {
+        form.submit();
+    };
+    const handleCancel = () => {
+        props.onCancel();
+    };
+    const handleFinish = async (values: Record<string, any>) => {
+        props.onSubmit(values as FormValueType);
+    };
+
+    return (
+        <Modal
+            width={640}
+            title={intl.formatMessage({
+                id: 'system.user.auth.role',
+                defaultMessage: '分配角色',
+            })}
+            open={props.open}
+            destroyOnClose
+            forceRender
+            onOk={handleOk}
+            onCancel={handleCancel}
+        >
+            <ProForm
+                form={form}
+                grid={true}
+                layout="horizontal"
+                onFinish={handleFinish}
+                initialValues={{
+                    login_password: '',
+                    confirm_password: '',
+                }}
+            >
+                <ProFormSelect
+                    name="roleIds"
+                    mode="multiple"
+                    label={intl.formatMessage({
+                        id: 'system.user.role',
+                        defaultMessage: '角色',
+                    })}
+                    options={props.roles}
+                    placeholder="请选择角色"
+                    rules={[{ required: true, message: '请选择角色!' }]}
+                />
+            </ProForm>
+        </Modal>
+    );
+};
+
+export default AuthRoleForm;
diff --git a/react-ui/src/pages/System/User/components/DeptTree.tsx b/react-ui/src/pages/System/User/components/DeptTree.tsx
new file mode 100644
index 0000000..d1a085c
--- /dev/null
+++ b/react-ui/src/pages/System/User/components/DeptTree.tsx
@@ -0,0 +1,69 @@
+import React, { useState, useEffect } from 'react';
+import { Tree, message } from 'antd';
+import { getDeptTree } from '@/services/system/user';
+
+const { DirectoryTree } = Tree;
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+
+export type TreeProps = {
+  onSelect: (values: any) => Promise<void>;
+};
+
+const DeptTree: React.FC<TreeProps> = (props) => {
+  const [treeData, setTreeData] = useState<any>([]);
+  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
+  const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
+
+  const fetchDeptList = async () => {
+    const hide = message.loading('正在查询');
+    try {
+      await getDeptTree({}).then((res: any) => {
+        const exKeys = [];
+        exKeys.push('1');
+        setTreeData(res);
+        exKeys.push(res[0].children[0].id);
+        setExpandedKeys(exKeys);
+        props.onSelect(res[0].children[0]);
+      });
+      hide();
+      return true;
+    } catch (error) {
+      hide();
+      return false;
+    }
+  };
+
+  useEffect(() => {
+    fetchDeptList();
+  }, []);
+
+  const onSelect = (keys: React.Key[], info: any) => {
+    props.onSelect(info.node);
+  };
+
+  const onExpand = (expandedKeysValue: React.Key[]) => {
+    setExpandedKeys(expandedKeysValue);
+    setAutoExpandParent(false);
+  };
+
+  return (
+    <DirectoryTree
+      // multiple
+      defaultExpandAll
+      onExpand={onExpand}
+      expandedKeys={expandedKeys}
+      autoExpandParent={autoExpandParent}
+      onSelect={onSelect}
+      treeData={treeData}
+    />
+  );
+};
+
+export default DeptTree;
diff --git a/react-ui/src/pages/System/User/components/ResetPwd.tsx b/react-ui/src/pages/System/User/components/ResetPwd.tsx
new file mode 100644
index 0000000..5523cd6
--- /dev/null
+++ b/react-ui/src/pages/System/User/components/ResetPwd.tsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import { Form, Modal } from 'antd';
+import { useIntl } from '@umijs/max';
+import { ProForm, ProFormText } from '@ant-design/pro-components';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+export type FormValueType = any & Partial<API.System.User>;
+
+export type UpdateFormProps = {
+  onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+  onSubmit: (values: FormValueType) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.User>;
+};
+
+const UpdateForm: React.FC<UpdateFormProps> = (props) => {
+  const [form] = Form.useForm();
+  const loginPassword = Form.useWatch('password', form);
+  const userId = props.values.userId;
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit({ ...values, userId } as FormValueType);
+  };
+
+  const checkPassword = (rule: any, value: string) => {
+    if (value === loginPassword) {
+      // 校验条件自定义
+      return Promise.resolve();
+    }
+    return Promise.reject(new Error('两次密码输入不一致'));
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.user.reset.password',
+        defaultMessage: '密码重置',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        grid={true}
+        form={form}
+        layout="horizontal"
+        onFinish={handleFinish}
+        initialValues={{
+          password: '',
+          confirm_password: '',
+        }}
+      >
+        <p>请输入用户{props.values.userName}的新密码!</p>
+        <ProFormText.Password
+          name="password"
+          label="登录密码"
+          rules={[
+            {
+              required: true,
+              message: '登录密码不可为空。',
+            },
+          ]}
+        />
+        <ProFormText.Password
+          name="confirm_password"
+          label="确认密码"
+          rules={[
+            {
+              required: true,
+              message: "确认密码",
+            },
+            { validator: checkPassword },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default UpdateForm;
diff --git a/react-ui/src/pages/System/User/edit.tsx b/react-ui/src/pages/System/User/edit.tsx
new file mode 100644
index 0000000..31d442b
--- /dev/null
+++ b/react-ui/src/pages/System/User/edit.tsx
@@ -0,0 +1,279 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormText,
+  ProFormSelect,
+  ProFormRadio,
+  ProFormTextArea,
+  ProFormTreeSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+
+export type UserFormData = Record<string, unknown> & Partial<API.System.User>;
+
+export type UserFormProps = {
+  onCancel: (flag?: boolean, formVals?: UserFormData) => void;
+  onSubmit: (values: UserFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.User>;
+  sexOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+  postIds: number[];
+  posts: string[];
+  roleIds: number[];
+  roles: string[];
+  depts: DataNode[];
+};
+
+const UserForm: React.FC<UserFormProps> = (props) => {
+  const [form] = Form.useForm();
+  const userId = Form.useWatch('userId', form);
+  const { sexOptions, statusOptions, } = props;
+  const { roles, posts, depts } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      userId: props.values.userId,
+      deptId: props.values.deptId,
+      postIds: props.postIds,
+      roleIds: props.roleIds,
+      userName: props.values.userName,
+      nickName: props.values.nickName,
+      email: props.values.email,
+      phonenumber: props.values.phonenumber,
+      sex: props.values.sex,
+      avatar: props.values.avatar,
+      status: props.values.status,
+      delFlag: props.values.delFlag,
+      loginIp: props.values.loginIp,
+      loginDate: props.values.loginDate,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as UserFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.user.title',
+        defaultMessage: '编辑用户信息',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        grid={true}
+        form={form}
+        layout="horizontal"
+        submitter={false}
+        onFinish={handleFinish}>
+        <ProFormText
+          name="nickName"
+          label={intl.formatMessage({
+            id: 'system.user.nick_name',
+            defaultMessage: '用户昵称',
+          })}
+          placeholder="请输入用户昵称"
+          colProps={{ xs: 24, md: 12, xl: 12 }}
+          rules={[
+            {
+              required: true,
+              message: (
+                <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormTreeSelect
+          name="deptId"
+          label={intl.formatMessage({
+            id: 'system.user.dept_name',
+            defaultMessage: '部门',
+          })}
+          request={async () => {
+            return depts;
+          }}
+          placeholder="请输入用户部门"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: true,
+              message: (
+                <FormattedMessage id="请输入用户部门!" defaultMessage="请输入用户部门!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormText
+          name="phonenumber"
+          label={intl.formatMessage({
+            id: 'system.user.phonenumber',
+            defaultMessage: '手机号码',
+          })}
+          placeholder="请输入手机号码"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: (
+                <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormText
+          name="email"
+          label={intl.formatMessage({
+            id: 'system.user.email',
+            defaultMessage: '用户邮箱',
+          })}
+          placeholder="请输入用户邮箱"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: (
+                <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormText
+          name="userName"
+          label={intl.formatMessage({
+            id: 'system.user.user_name',
+            defaultMessage: '用户账号',
+          })}
+          hidden={userId}
+          placeholder="请输入用户账号"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: true,
+            },
+          ]}
+        />
+        <ProFormText.Password
+          name="password"
+          label={intl.formatMessage({
+            id: 'system.user.password',
+            defaultMessage: '密码',
+          })}
+          hidden={userId}
+          placeholder="请输入密码"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入密码!" defaultMessage="请输入密码!" />,
+            },
+          ]}
+        />
+        <ProFormSelect
+          valueEnum={sexOptions}
+          name="sex"
+          label={intl.formatMessage({
+            id: 'system.user.sex',
+            defaultMessage: '用户性别',
+          })}
+          initialValue={'0'}
+          placeholder="请输入用户性别"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: (
+                <FormattedMessage id="请输入用户性别!" defaultMessage="请输入用户性别!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.user.status',
+            defaultMessage: '帐号状态',
+          })}
+          initialValue={'0'}
+          placeholder="请输入帐号状态"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: (
+                <FormattedMessage id="请输入帐号状态!" defaultMessage="请输入帐号状态!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormSelect
+          name="postIds"
+          mode="multiple"
+          label={intl.formatMessage({
+            id: 'system.user.post',
+            defaultMessage: '岗位',
+          })}
+          options={posts}
+          placeholder="请选择岗位"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[{ required: true, message: '请选择岗位!' }]}
+        />
+        <ProFormSelect
+          name="roleIds"
+          mode="multiple"
+          label={intl.formatMessage({
+            id: 'system.user.role',
+            defaultMessage: '角色',
+          })}
+          options={roles}
+          placeholder="请选择角色"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[{ required: true, message: '请选择角色!' }]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.user.remark',
+            defaultMessage: '备注',
+          })}
+          placeholder="请输入备注"
+          colProps={{ md: 24, xl: 24 }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default UserForm;
diff --git a/react-ui/src/pages/System/User/index.tsx b/react-ui/src/pages/System/User/index.tsx
new file mode 100644
index 0000000..6435787
--- /dev/null
+++ b/react-ui/src/pages/System/User/index.tsx
@@ -0,0 +1,561 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { Card, Col, Dropdown, FormInstance, Row, Space, Switch } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined } from '@ant-design/icons';
+import { getUserList, removeUser, addUser, updateUser, exportUser, getUser, changeUserStatus, updateAuthRole, resetUserPwd } from '@/services/system/user';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { DataNode } from 'antd/es/tree';
+import { getDeptTree } from '@/services/system/user';
+import DeptTree from './components/DeptTree';
+import ResetPwd from './components/ResetPwd';
+import { getPostList } from '@/services/system/post';
+import { getRoleList } from '@/services/system/role';
+import AuthRoleForm from './components/AuthRole';
+
+const { confirm } = Modal;
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ *
+ * */
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.User) => {
+  const hide = message.loading('正在添加');
+  try {
+    await addUser({ ...fields });
+    hide();
+    message.success('添加成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.User) => {
+  const hide = message.loading('正在配置');
+  try {
+    await updateUser(fields);
+    hide();
+    message.success('配置成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.User[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    await removeUser(selectedRows.map((row) => row.userId).join(','));
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.User) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.userId];
+    await removeUser(params.join(','));
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportUser();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+const UserTableList: React.FC = () => {
+  const [messageApi, contextHolder] = message.useMessage();
+
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+  const [resetPwdModalVisible, setResetPwdModalVisible] = useState<boolean>(false);
+  const [authRoleModalVisible, setAuthRoleModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.User>();
+  const [selectedRows, setSelectedRows] = useState<API.System.User[]>([]);
+
+  const [selectDept, setSelectDept] = useState<any>({ id: 0 });
+  const [sexOptions, setSexOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const [postIds, setPostIds] = useState<number[]>();
+  const [postList, setPostList] = useState<any[]>();
+  const [roleIds, setRoleIds] = useState<number[]>();
+  const [roleList, setRoleList] = useState<any[]>();
+  const [deptTree, setDeptTree] = useState<DataNode[]>();
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_user_sex').then((data) => {
+      setSexOptions(data);
+    });
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const showChangeStatusConfirm = (record: API.System.User) => {
+    let text = record.status === "1" ? "启用" : "停用";
+    const newStatus = record.status === '0' ? '1' : '0';
+    confirm({
+      title: `确认要${text}${record.userName}用户吗?`,
+      onOk() {
+        changeUserStatus(record.userId, newStatus).then(resp => {
+          if (resp.code === 200) {
+            messageApi.open({
+              type: 'success',
+              content: '更新成功!',
+            });
+            actionRef.current?.reload();
+          } else {
+            messageApi.open({
+              type: 'error',
+              content: '更新失败!',
+            });
+          }
+        });
+      },
+    });
+  };
+
+  const fetchUserInfo = async (userId: number) => {
+    const res = await getUser(userId);
+    setPostIds(res.postIds);
+    setPostList(
+      res.posts.map((item: any) => {
+        return {
+          value: item.postId,
+          label: item.postName,
+        };
+      }),
+    );
+    setRoleIds(res.roleIds);
+    setRoleList(
+      res.roles.map((item: any) => {
+        return {
+          value: item.roleId,
+          label: item.roleName,
+        };
+      }),
+    );
+  };
+
+  const columns: ProColumns<API.System.User>[] = [
+    {
+      title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+      dataIndex: 'deptId',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+      dataIndex: 'nickName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.dept_name" defaultMessage="部门" />,
+      dataIndex: ['dept', 'deptName'],
+      valueType: 'text',
+      hideInSearch: true
+    },
+    {
+      title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+      dataIndex: 'phonenumber',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (
+          <Switch
+            checked={record.status === '0'}
+            checkedChildren="正常"
+            unCheckedChildren="停用"
+            defaultChecked
+            onClick={() => showChangeStatusConfirm(record)}
+          />)
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          icon=<EditOutlined />
+          hidden={!access.hasPerms('system:user:edit')}
+          onClick={async () => {
+            fetchUserInfo(record.userId);
+            const treeData = await getDeptTree({});
+            setDeptTree(treeData);
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          icon=<DeleteOutlined />
+          key="batchRemove"
+          hidden={!access.hasPerms('system:user:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+        <Dropdown
+          key="more"
+          menu={{
+            items: [
+              {
+                label: <FormattedMessage id="system.user.reset.password" defaultMessage="密码重置" />,
+                key: 'reset',
+                disabled: !access.hasPerms('system:user:edit'),
+              },
+              {
+                label: '分配角色',
+                key: 'authRole',
+                disabled: !access.hasPerms('system:user:edit'),
+              },
+            ],
+            onClick: ({ key }) => {
+              if (key === 'reset') {
+                setResetPwdModalVisible(true);
+                setCurrentRow(record);
+              }
+              else if (key === 'authRole') {
+                fetchUserInfo(record.userId);
+                setAuthRoleModalVisible(true);
+                setCurrentRow(record);
+              }
+            }
+          }}
+        >
+          <a onClick={(e) => e.preventDefault()}>
+            <Space>
+              <DownOutlined />
+              更多
+            </Space>
+          </a>
+        </Dropdown>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      {contextHolder}
+      <Row gutter={[16, 24]}>
+        <Col lg={6} md={24}>
+          <Card>
+            <DeptTree
+              onSelect={async (value: any) => {
+                setSelectDept(value);
+                if (actionRef.current) {
+                  formTableRef?.current?.submit();
+                }
+              }}
+            />
+          </Card>
+        </Col>
+        <Col lg={18} md={24}>
+          <ProTable<API.System.User>
+            headerTitle={intl.formatMessage({
+              id: 'pages.searchTable.title',
+              defaultMessage: '信息',
+            })}
+            actionRef={actionRef}
+            formRef={formTableRef}
+            rowKey="userId"
+            key="userList"
+            search={{
+              labelWidth: 120,
+            }}
+            toolBarRender={() => [
+              <Button
+                type="primary"
+                key="add"
+                hidden={!access.hasPerms('system:user:add')}
+                onClick={async () => {
+                  const treeData = await getDeptTree({});
+                  setDeptTree(treeData);
+
+                  const postResp = await getPostList()
+                  if (postResp.code === 200) {
+                    setPostList(
+                      postResp.rows.map((item: any) => {
+                        return {
+                          value: item.postId,
+                          label: item.postName,
+                        };
+                      }),
+                    );
+                  }
+
+                  const roleResp = await getRoleList()
+                  if (roleResp.code === 200) {
+                    setRoleList(
+                      roleResp.rows.map((item: any) => {
+                        return {
+                          value: item.roleId,
+                          label: item.roleName,
+                        };
+                      }),
+                    );
+                  }
+                  setCurrentRow(undefined);
+                  setModalVisible(true);
+                }}
+              >
+                <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+              </Button>,
+              <Button
+                type="primary"
+                key="remove"
+                danger
+                hidden={selectedRows?.length === 0 || !access.hasPerms('system:user:remove')}
+                onClick={async () => {
+                  Modal.confirm({
+                    title: '是否确认删除所选数据项?',
+                    icon: <ExclamationCircleOutlined />,
+                    content: '请谨慎操作',
+                    async onOk() {
+                      const success = await handleRemove(selectedRows);
+                      if (success) {
+                        setSelectedRows([]);
+                        actionRef.current?.reloadAndRest?.();
+                      }
+                    },
+                    onCancel() { },
+                  });
+                }}
+              >
+                <DeleteOutlined />
+                <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+              </Button>,
+              <Button
+                type="primary"
+                key="export"
+                hidden={!access.hasPerms('system:user:export')}
+                onClick={async () => {
+                  handleExport();
+                }}
+              >
+                <PlusOutlined />
+                <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+              </Button>,
+            ]}
+            request={(params) =>
+              getUserList({ ...params, deptId: selectDept.id } as API.System.UserListParams).then((res) => {
+                const result = {
+                  data: res.rows,
+                  total: res.total,
+                  success: true,
+                };
+                return result;
+              })
+            }
+            columns={columns}
+            rowSelection={{
+              onChange: (_, selectedRows) => {
+                setSelectedRows(selectedRows);
+              },
+            }}
+          />
+        </Col>
+      </Row>
+      {selectedRows?.length > 0 && (
+        <FooterToolbar
+          extra={
+            <div>
+              <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+              <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+              <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+            </div>
+          }
+        >
+          <Button
+            key="remove"
+            danger
+            hidden={!access.hasPerms('system:user:del')}
+            onClick={async () => {
+              Modal.confirm({
+                title: '删除',
+                content: '确定删除该项吗?',
+                okText: '确认',
+                cancelText: '取消',
+                onOk: async () => {
+                  const success = await handleRemove(selectedRows);
+                  if (success) {
+                    setSelectedRows([]);
+                    actionRef.current?.reloadAndRest?.();
+                  }
+                },
+              });
+            }}
+          >
+            <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+          </Button>
+        </FooterToolbar>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.userId) {
+            success = await handleUpdate({ ...values } as API.System.User);
+          } else {
+            success = await handleAdd({ ...values } as API.System.User);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        sexOptions={sexOptions}
+        statusOptions={statusOptions}
+        posts={postList || []}
+        postIds={postIds || []}
+        roles={roleList || []}
+        roleIds={roleIds || []}
+        depts={deptTree || []}
+      />
+      <ResetPwd
+        onSubmit={async (values: any) => {
+          const success = await resetUserPwd(values.userId, values.password);
+          if (success) {
+            setResetPwdModalVisible(false);
+            setSelectedRows([]);
+            setCurrentRow(undefined);
+            message.success('密码重置成功。');
+          }
+        }}
+        onCancel={() => {
+          setResetPwdModalVisible(false);
+          setSelectedRows([]);
+          setCurrentRow(undefined);
+        }}
+        open={resetPwdModalVisible}
+        values={currentRow || {}}
+      />
+      <AuthRoleForm
+        onSubmit={async (values: any) => {
+          const success = await updateAuthRole(values);
+          if (success) {
+            setAuthRoleModalVisible(false);
+            setSelectedRows([]);
+            setCurrentRow(undefined);
+            message.success('配置成功。');
+          }
+        }}
+        onCancel={() => {
+          setAuthRoleModalVisible(false);
+          setSelectedRows([]);
+          setCurrentRow(undefined);
+        }}
+        open={authRoleModalVisible}
+        roles={roleList || []}
+        roleIds={roleIds || []}
+      />
+    </PageContainer>
+  );
+};
+
+export default UserTableList;
diff --git a/react-ui/src/pages/Tool/Gen/components/BaseInfo.tsx b/react-ui/src/pages/Tool/Gen/components/BaseInfo.tsx
new file mode 100644
index 0000000..6a0466f
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/components/BaseInfo.tsx
@@ -0,0 +1,124 @@
+import { Button, Col, Form, message, Row } from 'antd';
+import React, { Fragment, useEffect } from 'react';
+import { history } from '@umijs/max';
+import styles from '../style.less';
+import { ProForm, ProFormText, ProFormTextArea } from '@ant-design/pro-components';
+
+export type BaseInfoProps = {
+  values?: any;
+  onStepSubmit?: any;
+};
+
+const BaseInfo: React.FC<BaseInfoProps> = (props) => {
+  const [form] = Form.useForm();
+  const { onStepSubmit } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      tableName: props.values.tableName,
+    });
+  });
+
+  const onValidateForm = async () => {
+    const values = await form.validateFields();
+    if (onStepSubmit) {
+      onStepSubmit('base', values);
+    }
+  };
+
+  return (
+    <Fragment>
+      <Row>
+        <Col span={24}>
+          <ProForm
+            form={form}
+            onFinish={async () => {
+              message.success('提交成功');
+            }}
+            initialValues={{
+              tableName: props.values?.tableName,
+              tableComment: props.values?.tableComment,
+              className: props.values?.className,
+              functionAuthor: props.values?.functionAuthor,
+              remark: props.values?.remark,
+            }}
+            submitter={{
+              resetButtonProps: {
+                style: { display: 'none' },
+              },
+              submitButtonProps: {
+                style: { display: 'none' },
+              },
+            }}
+          >
+            <Row>
+              <Col span={12} order={1}>
+                <ProFormText
+
+                  name="tableName"
+                  label="表名称"
+                  rules={[
+                    {
+                      required: true,
+                      message: '表名称不可为空。',
+                    },
+                  ]}
+                />
+              </Col>
+              <Col span={12} order={2}>
+                <ProFormText
+                  name="tableComment"
+                  label="表描述"
+                />
+              </Col>
+            </Row>
+            <Row>
+              <Col span={12} order={1}>
+                <ProFormText
+
+                  name="className"
+                  label="实体类名称"
+                  rules={[
+                    {
+                      required: true,
+                      message: '实体类名称不可为空。',
+                    },
+                  ]}
+                />
+              </Col>
+              <Col span={12} order={2}>
+                <ProFormText name="functionAuthor" label="作者" />
+              </Col>
+            </Row>
+            <Row>
+              <Col span={24}>
+                <ProFormTextArea name="remark" label="备注" />
+              </Col>
+            </Row>
+          </ProForm>
+        </Col>
+      </Row>
+      <Row justify="center">
+        <Col span={4}>
+          <Button
+            type="primary"
+            className={styles.step_buttons}
+            onClick={() => {
+              history.back();
+            }}
+          >
+            返回
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button type="primary" onClick={onValidateForm}>
+            下一步
+          </Button>
+        </Col>
+      </Row>
+    </Fragment>
+  );
+};
+
+export default BaseInfo;
diff --git a/react-ui/src/pages/Tool/Gen/components/ColumnInfo.tsx b/react-ui/src/pages/Tool/Gen/components/ColumnInfo.tsx
new file mode 100644
index 0000000..2db5de4
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/components/ColumnInfo.tsx
@@ -0,0 +1,304 @@
+import React, { Fragment, useEffect, useRef, useState } from 'react';
+import type { GenCodeType } from '../data';
+import { Button, Checkbox, Col, Row, Tag } from 'antd';
+import type { FormInstance } from 'antd';
+import { history } from '@umijs/max';
+import styles from '../style.less';
+import { EditableProTable, ProColumns } from '@ant-design/pro-components';
+
+export type ColumnInfoProps = {
+  parentType?: string;
+  data?: any[];
+  dictData?: any[];
+  onStepSubmit?: any;
+};
+
+const booleanEnum = [
+  {
+    label: 'true',
+    value: '1',
+  },
+  {
+    label: 'false',
+    value: '0',
+  },
+];
+
+const ColumnInfo: React.FC<ColumnInfoProps> = (props) => {
+  const formRef = useRef<FormInstance>();
+
+  const [dataSource, setDataSource] = useState<any[]>();
+
+  const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
+
+  const { data, dictData, onStepSubmit } = props;
+
+  const columns: ProColumns<GenCodeType>[] = [
+    {
+      title: '编号',
+      dataIndex: 'columnId',
+      editable: false,
+      width: 80,
+    },
+    {
+      title: '字段名',
+      dataIndex: 'columnName',
+      editable: false,
+    },
+    {
+      title: '字段描述',
+      dataIndex: 'columnComment',
+      hideInForm: true,
+      hideInSearch: true,
+      width: 200,
+    },
+    {
+      title: '字段类型',
+      dataIndex: 'columnType',
+      editable: false,
+    },
+    {
+      title: 'Java类型',
+      dataIndex: 'javaType',
+      valueType: 'select',
+      valueEnum: {
+        Long: {
+          text: 'Long',
+        },
+        String: {
+          text: 'String',
+        },
+        Integer: {
+          text: 'Integer',
+        },
+        Double: {
+          text: 'Double',
+        },
+        BigDecimal: {
+          text: 'BigDecimal',
+        },
+        Date: {
+          text: 'Date',
+        },
+      },
+    },
+    {
+      title: 'Java属性',
+      dataIndex: 'javaField',
+    },
+    {
+      title: '插入',
+      dataIndex: 'isInsert',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isInsert === '1'} />;
+      },
+    },
+    {
+      title: '编辑',
+      dataIndex: 'isEdit',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isEdit === '1'} />;
+      },
+    },
+    {
+      title: '列表',
+      dataIndex: 'isList',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isList === '1'} />;
+      },
+    },
+    {
+      title: '查询',
+      dataIndex: 'isQuery',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isQuery === '1'} />;
+      },
+    },
+    {
+      title: '查询方式',
+      dataIndex: 'queryType',
+      valueType: 'select',
+      valueEnum: {
+        EQ: {
+          text: '=',
+        },
+        NE: {
+          text: '!=',
+        },
+        GT: {
+          text: '>',
+        },
+        GTE: {
+          text: '>=',
+        },
+        LT: {
+          text: '<',
+        },
+        LTE: {
+          text: '<=',
+        },
+        LIKE: {
+          text: 'LIKE',
+        },
+        BETWEEN: {
+          text: 'BETWEEN',
+        },
+      },
+    },
+    {
+      title: '必填',
+      dataIndex: 'isRequired',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isRequired === '1'} />;
+      },
+    },
+    {
+      title: '显示类型',
+      dataIndex: 'htmlType',
+      hideInSearch: true,
+      valueType: 'select',
+      valueEnum: {
+        input: {
+          text: '文本框',
+        },
+        textarea: {
+          text: '文本域',
+        },
+        select: {
+          text: '下拉框',
+        },
+        radio: {
+          text: '单选框',
+        },
+        checkbox: {
+          text: '复选框',
+        },
+        datetime: {
+          text: '日期控件',
+        },
+        imageUpload: {
+          text: '图片上传',
+        },
+        fileUpload: {
+          text: '文件上传',
+        },
+        editor: {
+          text: '富文本控件',
+        },
+      },
+    },
+    {
+      title: '字典类型',
+      dataIndex: 'dictType',
+      hideInSearch: true,
+      valueType: 'select',
+      fieldProps: {
+        options: dictData,
+      },
+      render: (text) => {
+        return <Tag color="#108ee9">{text}</Tag>;
+      },
+    },
+  ];
+
+  useEffect(() => {
+    setDataSource(data);
+    if (data) {
+      setEditableRowKeys(data.map((item) => item.columnId));
+    }
+  }, [data]);
+
+  const onSubmit = (direction: string) => {
+    if (onStepSubmit) {
+      onStepSubmit('column', dataSource, direction);
+    }
+  };
+
+  const onDataChange = (value: readonly GenCodeType[]) => {
+    setDataSource({ ...value } as []);
+  };
+
+  return (
+    <Fragment>
+      <Row>
+        <Col span={24}>
+          <EditableProTable<GenCodeType>
+            formRef={formRef}
+            rowKey="columnId"
+            search={false}
+            columns={columns}
+            value={dataSource}
+            editable={{
+              type: 'multiple',
+              editableKeys,
+              onChange: setEditableRowKeys,
+              actionRender: (row, config, defaultDoms) => {
+                return [defaultDoms.delete];
+              },
+              onValuesChange: (record, recordList) => {
+                setDataSource(recordList);
+              },
+            }}
+            onChange={onDataChange}
+            recordCreatorProps={false}
+          />
+        </Col>
+      </Row>
+      <Row justify="center">
+        <Col span={4}>
+          <Button
+            type="primary"
+            onClick={() => {
+              history.back();
+            }}
+          >
+            返回
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button
+            type="primary"
+            className={styles.step_buttons}
+            onClick={() => {
+              onSubmit('prev');
+            }}
+          >
+            上一步
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button
+            type="primary"
+            onClick={() => {
+              onSubmit('next');
+            }}
+          >
+            下一步
+          </Button>
+        </Col>
+      </Row>
+    </Fragment>
+  );
+};
+
+export default ColumnInfo;
diff --git a/react-ui/src/pages/Tool/Gen/components/GenInfo.tsx b/react-ui/src/pages/Tool/Gen/components/GenInfo.tsx
new file mode 100644
index 0000000..97901ec
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/components/GenInfo.tsx
@@ -0,0 +1,330 @@
+import { Button, Col, Divider, Form, Row, TreeSelect } from 'antd';
+import React, { Fragment, useEffect, useState } from 'react';
+import { history } from '@umijs/max';
+import type { TableInfo } from '../data';
+import styles from '../style.less';
+import { DataNode } from 'antd/es/tree';
+import { ProForm, ProFormRadio, ProFormSelect, ProFormText } from '@ant-design/pro-components';
+
+export type GenInfoProps = {
+  values?: any;
+  menuData?: DataNode[];
+  tableInfo?: TableInfo[];
+  onStepSubmit?: any;
+};
+
+const GenInfo: React.FC<GenInfoProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const [pathType, setPathType] = useState<string>('0');
+  const [tlpType, setTlpType] = useState<string>('curd');
+
+  const [subTablesColumnOptions, setSubTablesColumnOptions] = useState<any[]>();
+
+  const { menuData, tableInfo, onStepSubmit } = props;
+
+  const tablesOptions = tableInfo?.map((item: any) => {
+    return {
+      value: item.tableName,
+      label: `${item.tableName}:${item.tableComment}`,
+    };
+  });
+
+  if (tableInfo) {
+    for (let index = 0; index < tableInfo?.length; index += 1) {
+      const tbl = tableInfo[index];
+      if (tbl.tableName === props.values.subTableName) {
+        const opts = [];
+        tbl.columns.forEach((item) => {
+          opts.push({
+            value: item.columnName,
+            label: `${item.columnName}: ${item.columnComment}`,
+          });
+        });
+        break;
+      }
+    }
+  }
+
+  const treeColumns = props.values.columns.map((item: any) => {
+    return {
+      value: item.columnName,
+      label: `${item.columnName}: ${item.columnComment}`,
+    };
+  });
+
+  const onSubmit = async (direction: string) => {
+    const values = await form.validateFields();
+    onStepSubmit('gen', values, direction);
+  };
+
+  useEffect(() => {
+    setPathType(props.values.genType);
+    setTlpType(props.values.tplCategory);
+  }, [props.values.genType, props.values.tplCategory]);
+
+  return (
+    <Fragment>
+      <Row>
+        <Col span={24}>
+          <ProForm
+            form={form}
+            onFinish={async () => {
+              const values = await form.validateFields();
+              onStepSubmit('gen', values);
+            }}
+            initialValues={{
+              curd: props.values.curd,
+              tree: props.values.tree,
+              sub: props.values.sub,
+              tplCategory: props.values.tplCategory,
+              packageName: props.values.packageName,
+              moduleName: props.values.moduleName,
+              businessName: props.values.businessName,
+              functionName: props.values.functionName,
+              parentMenuId: props.values.parentMenuId,
+              genType: props.values.genType,
+              genPath: props.values.genPath,
+              treeCode: props.values.treeCode,
+              treeParentCode: props.values.treeParentCode,
+              treeName: props.values.treeName,
+              subTableName: props.values.subTableName,
+              subTableFkName: props.values.subTableFkName,
+            }}
+            submitter={{
+              resetButtonProps: {
+                style: { display: 'none' },
+              },
+              submitButtonProps: {
+                style: { display: 'none' },
+              },
+            }}
+          >
+            <Row gutter={[16, 16]}>
+              <Col span={12} order={1}>
+                <ProFormSelect
+                  fieldProps={{
+                    onChange: (val) => {
+                      setTlpType(val);
+                    },
+                  }}
+                  valueEnum={{
+                    crud: '单表(增删改查)',
+                    tree: '树表(增删改查)',
+                    sub: '主子表(增删改查)',
+                  }}
+                  name="tplCategory"
+                  label="生成模板"
+                  rules={[
+                    {
+                      required: true,
+                      message: '选择类型',
+                    },
+                  ]}
+                />
+              </Col>
+              <Col span={12} order={2}>
+                <ProFormText name="packageName" label="生成包路径" />
+              </Col>
+            </Row>
+            <Row gutter={[16, 16]}>
+              <Col span={12} order={1}>
+                <ProFormText name="moduleName" label="生成模块名" />
+              </Col>
+              <Col span={12} order={2}>
+                <ProFormText name="businessName" label="生成业务名" />
+              </Col>
+            </Row>
+            <Row gutter={[16, 16]}>
+              <Col span={12} order={1}>
+                <ProFormText name="functionName" label="生成功能名" />
+              </Col>
+              <Col span={12} order={2}>
+                <ProForm.Item
+                  labelCol={{ span: 20 }}
+                  name="parentMenuId"
+                  label="父菜单"
+                >
+                  <TreeSelect
+                    style={{ width: '74%' }}
+                    defaultValue={props.values.parentMenuId}
+                    treeData={menuData}
+                    placeholder="请选择父菜单"
+                  />
+                </ProForm.Item>
+              </Col>
+            </Row>
+            <Row gutter={[16, 16]}>
+              <Col span={24}>
+                <ProFormRadio.Group
+                  valueEnum={{
+                    '0': 'zip压缩包',
+                    '1': '自定义路径',
+                  }}
+                  name="genType"
+                  label="生成代码方式"
+                  rules={[
+                    {
+                      required: true,
+                      message: '选择类型',
+                    },
+                  ]}
+                  fieldProps={{
+                    onChange: (e) => {
+                      setPathType(e.target.value);
+                    },
+                  }}
+                />
+              </Col>
+            </Row>
+            <Row gutter={[16, 16]}>
+              <Col span={24} order={1}>
+                <ProFormText
+                  hidden={pathType === '0'}
+                  width="md"
+                  name="genPath"
+                  label="自定义路径"
+                />
+              </Col>
+            </Row>
+            <div hidden={tlpType !== 'tree'}>
+              <Divider orientation="left">其他信息</Divider>
+              <Row gutter={[16, 16]}>
+                <Col span={12} order={1}>
+                  <ProFormSelect
+                    name="treeCode"
+                    label="树编码字段"
+                    options={treeColumns}
+                    rules={[
+                      {
+                        required: tlpType === 'tree',
+                        message: '树编码字段',
+                      },
+                    ]}
+                  />
+                </Col>
+                <Col span={12} order={2}>
+                  <ProFormSelect
+                    name="treeParentCode"
+                    label="树父编码字段"
+                    options={treeColumns}
+                    rules={[
+                      {
+                        required: tlpType === 'tree',
+                        message: '树父编码字段',
+                      },
+                    ]}
+                  />
+                </Col>
+              </Row>
+              <Row gutter={[16, 16]}>
+                <Col span={12} order={1}>
+                  <ProFormSelect
+                    name="treeName"
+                    label="树名称字段"
+                    options={treeColumns}
+                    rules={[
+                      {
+                        required: tlpType === 'tree',
+                        message: '树名称字段',
+                      },
+                    ]}
+                  />
+                </Col>
+              </Row>
+            </div>
+            <div hidden={tlpType !== 'sub'}>
+              <Divider orientation="left">关联信息</Divider>
+              <Row gutter={[16, 16]}>
+                <Col span={12} order={1}>
+                  <ProFormSelect
+                    name="subTableName"
+                    label="关联子表的表名"
+                    options={tablesOptions}
+                    rules={[
+                      {
+                        required: tlpType === 'sub',
+                        message: '关联子表的表名',
+                      },
+                    ]}
+                    fieldProps={{
+                      onChange: (val) => {
+                        form.setFieldsValue({
+                          subTableFkName: '',
+                        });
+                        if (tableInfo) {
+                          for (let index = 0; index < tableInfo?.length; index += 1) {
+                            const tbl = tableInfo[index];
+                            if (tbl.tableName === val) {
+                              const opts: any[] = [];
+                              tbl.columns.forEach((item) => {
+                                opts.push({
+                                  value: item.columnName,
+                                  label: `${item.columnName}:${item.columnComment}`,
+                                });
+                              });
+                              setSubTablesColumnOptions(opts);
+                              break;
+                            }
+                          }
+                        }
+                      },
+                    }}
+                  />
+                </Col>
+                <Col span={12} order={2}>
+                  <ProFormSelect
+                    name="subTableFkName"
+                    options={subTablesColumnOptions}
+                    label="子表关联的外键名"
+                    rules={[
+                      {
+                        required: tlpType === 'sub',
+                        message: '子表关联的外键名',
+                      },
+                    ]}
+                  />
+                </Col>
+              </Row>
+            </div>
+          </ProForm>
+        </Col>
+      </Row>
+      <Row justify="center">
+        <Col span={4}>
+          <Button
+            type="primary"
+            onClick={() => {
+              history.back();
+            }}
+          >
+            返回
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button
+            type="primary"
+            className={styles.step_buttons}
+            onClick={() => {
+              onSubmit('prev');
+            }}
+          >
+            上一步
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button
+            type="primary"
+            onClick={() => {
+              onSubmit('next');
+            }}
+          >
+            提交
+          </Button>
+        </Col>
+      </Row>
+    </Fragment>
+  );
+};
+
+export default GenInfo;
diff --git a/react-ui/src/pages/Tool/Gen/components/PreviewCode.tsx b/react-ui/src/pages/Tool/Gen/components/PreviewCode.tsx
new file mode 100644
index 0000000..f3611ad
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/components/PreviewCode.tsx
@@ -0,0 +1,50 @@
+import React, { useEffect } from 'react';
+import { useIntl } from '@umijs/max';
+import { Modal, Tabs } from 'antd';
+import type { TabsProps } from 'antd';
+import Highlight from 'react-highlight';
+import 'highlight.js/styles/base16/material.css';
+
+interface PreviewTableProps {
+  open: boolean;
+  data?: any;
+  onHide: () => void;
+}
+
+const PreviewTableCode: React.FC<PreviewTableProps> = (props) => {
+  const intl = useIntl();
+  const panes: any = [];
+  const keys = Object.keys(props.data);
+  keys.forEach((key) => {
+    panes.push({
+        key: key + '1',
+        label: key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm')),
+        children: <Highlight className="java">{props.data[key]}</Highlight>,
+      } as TabsProps);
+  });
+
+  useEffect(() => {}, []);
+
+  return (
+    <Modal
+      width={900}
+      title={intl.formatMessage({
+        id: 'gen.preview',
+        defaultMessage: '预览',
+      })}
+      open={props.open}
+      destroyOnClose
+      footer={false}
+      onOk={() => {
+        props.onHide();
+      }}
+      onCancel={() => {
+        props.onHide();
+      }}
+    >
+      <Tabs defaultActiveKey="1" items={panes}></Tabs>
+    </Modal>
+  );
+};
+
+export default PreviewTableCode;
diff --git a/react-ui/src/pages/Tool/Gen/data.d.ts b/react-ui/src/pages/Tool/Gen/data.d.ts
new file mode 100644
index 0000000..a63ea9d
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/data.d.ts
@@ -0,0 +1,106 @@
+export type GenCodeType = {
+  columnId: string;
+  businessName: string;
+  className: string;
+  createBy: string;
+  createTime: string;
+  crud: string;
+  functionAuthor: string;
+  functionName: string;
+  genPath: string;
+  genType: string;
+  moduleName: string;
+  options: string;
+  packageName: string;
+  params: string;
+  parentMenuId: string;
+  parentMenuName: string;
+  remark: string;
+  status: string;
+  isList: string;
+  isEdit: string;
+  isQuery: string;
+  isInsert: string;
+  isRequired: string;
+  tableComment: string;
+  tableId: string;
+  tableName: string;
+  tplCategory: string;
+  tree: string;
+  treeCode: number;
+  treeName: string;
+  treeParentCode: number;
+  updateBy: string;
+  updateTime: string;
+  columns: any;
+};
+
+export type GenCodePagination = {
+  total: number;
+  pageSize: number;
+  current: number;
+};
+
+export type GenCodeListData = {
+  list: GenCodeType[];
+  pagination: Partial<GenCodePagination>;
+};
+
+export type GenCodeTableListParams = {
+  businessName?: string;
+  className?: string;
+  createBy?: string;
+  createTime?: string;
+  crud?: string;
+  functionAuthor?: string;
+  functionName?: string;
+  genPath?: string;
+  genType?: string;
+  moduleName?: string;
+  isList?: string;
+  isEdit?: string;
+  isQuery?: string;
+  isInsert?: string;
+  isRequired?: string;
+  options?: string;
+  params?: any;
+  packageName?: string;
+  parentMenuId?: string;
+  columns?: any;
+  parentMenuName?: string;
+  remark?: string;
+  tableComment?: string;
+  tableId?: string;
+  tableName?: string;
+  tplCategory?: string;
+  tree?: string;
+  treeCode?: string;
+  treeName?: string;
+  treeParentCode?: string;
+  updateBy?: string;
+  updateTime?: string;
+  pageSize?: string;
+  currentPage?: string;
+  filter?: string;
+  sorter?: string;
+};
+
+export type TableColumnInfo = {
+  columnName: string;
+  columnComment: string;
+};
+
+export type TableInfo = {
+  tableName: string;
+  tableComment: string;
+  columns: TableColumnInfo[];
+};
+
+export type ImportTableListItem = {
+  tableName: string;
+  tableComment: string;
+};
+
+export type ImportTableParams = {
+  tables: string;
+};
diff --git a/react-ui/src/pages/Tool/Gen/edit.tsx b/react-ui/src/pages/Tool/Gen/edit.tsx
new file mode 100644
index 0000000..99a0d1a
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/edit.tsx
@@ -0,0 +1,183 @@
+import React, { useEffect, useState } from 'react';
+import BaseInfo from './components/BaseInfo';
+import { Card, Layout, message, Steps } from 'antd';
+import ColumnInfo from './components/ColumnInfo';
+import GenInfo from './components/GenInfo';
+import { getGenCode, updateData } from './service';
+import { formatTreeData } from '@/utils/tree';
+import styles from './style.less';
+import type { GenCodeType } from './data';
+import { getMenuTree } from '@/services/system/menu';
+import { getDictTypeList } from '@/services/system/dict';
+import queryString from 'query-string';
+import { useLocation } from '@umijs/max';
+
+const { Content } = Layout;
+
+export type GenCodeArgs = {
+  id: string;
+};
+
+const TableList: React.FC = () => {
+  const location = useLocation();
+  const query = queryString.parse(location.search);
+  const { id } = query as GenCodeArgs;
+  const tableId = id;
+
+  const [currentStep, setCurrentStep] = useState<number>(0);
+  const [columnData, setColumnData] = useState<any>([]);
+  const [baseInfoData, setBaseInfoData] = useState<any>([]);
+  const [genInfoData, setGenInfoData] = useState<any>([]);
+  const [menuTree, setMenuTree] = useState<any>([]);
+  const [dictData, setDictData] = useState<any>([]);
+  const [tableInfo, setTableInfo] = useState<any>([]);
+  const [formData, setFormData] = useState<any>([]);
+  const [stepComponent, setStepComponent] = useState<any>([]);
+  const [stepKey, setStepKey] = useState<string>('');
+
+  const getCurrentStepAndComponent = (key?: string) => {
+    if (key === 'base') {
+      return (
+        <BaseInfo
+          values={baseInfoData}
+          // eslint-disable-next-line @typescript-eslint/no-use-before-define
+          onStepSubmit={onNextStep}
+        />
+      );
+    }
+    if (key === 'column') {
+      return (
+        <ColumnInfo
+          data={columnData}
+          dictData={dictData}
+          // eslint-disable-next-line @typescript-eslint/no-use-before-define
+          onStepSubmit={onNextStep}
+        />
+      );
+    }
+    if (key === 'gen') {
+      return (
+        <GenInfo
+          values={genInfoData}
+          menuData={menuTree}
+          tableInfo={tableInfo}
+          // eslint-disable-next-line @typescript-eslint/no-use-before-define
+          onStepSubmit={onNextStep}
+        />
+      );
+    }
+    return null;
+  };
+
+  const onNextStep = (step: string, values: any, direction: string) => {
+    let stepKey = 'base';
+    if (step === 'base') {
+      setStepKey('column');
+      setCurrentStep(1);
+      setFormData(values);
+      setStepComponent(getCurrentStepAndComponent(stepKey));
+    } else if (step === 'column') {
+      if (direction === 'prev') {
+        setStepKey('base');
+        setCurrentStep(0);
+      } else {
+        setStepKey('gen');
+        const tableData: GenCodeType = formData || ({} as GenCodeType);
+        tableData.columns = values;
+        setCurrentStep(2);
+        setFormData(tableData);
+      }
+      setStepComponent(getCurrentStepAndComponent(stepKey));
+    } else if (step === 'gen') {
+      if (direction === 'prev') {
+        setStepKey('column');
+        setCurrentStep(1);
+        setStepComponent(getCurrentStepAndComponent(stepKey));
+      } else {
+        const postData: GenCodeType = {
+          ...formData,
+          ...values,
+          params: values,
+          tableId: tableId,
+        };
+        setFormData(postData);
+        updateData({ ...postData } as GenCodeType).then((res) => {
+          if (res.code === 200) {
+            message.success('提交成功');
+            history.back();
+          } else {
+            message.success('提交失败');
+          }
+        });
+      }
+    }
+  };
+  useEffect(() => {
+    setStepComponent(getCurrentStepAndComponent(stepKey));
+  }, [stepKey]);
+
+  useEffect(() => {
+    getGenCode(tableId).then((res) => {
+      if (res.code === 200) {
+        setBaseInfoData(res.data.info);
+        setColumnData(res.data.rows);
+        setGenInfoData(res.data.info);
+        setTableInfo(res.data.tables);
+        setStepKey('base');
+      } else {
+        message.error(res.msg);
+      }
+    });
+    getMenuTree().then((res) => {
+      if (res.code === 200) {
+        const treeData = formatTreeData(res.data);
+        setMenuTree(treeData);
+      } else {
+        message.error(res.msg);
+      }
+    });
+
+    getDictTypeList().then((res: any) => {
+      if (res.code === 200) {
+        const dicts = res.rows.map((item: any) => {
+          return {
+            label: item.dictName,
+            value: item.dictType,
+          };
+        });
+        setDictData(dicts);
+      } else {
+        message.error(res.msg);
+      }
+    });
+  }, []);
+
+  // const onFinish = (values: any) => {
+  //   console.log('Success:', values);
+  // };
+
+  // const onFinishFailed = (errorInfo: any) => {
+  //   console.log('Failed:', errorInfo);
+  // };
+
+  return (
+    <Content>
+      <Card className={styles.tabsCard} bordered={false}>
+        <Steps current={currentStep} className={styles.steps} items={[
+          {
+            title: '基本信息',
+          },
+          {
+            title: '字段信息',
+          },
+          {
+            title: '生成信息',
+          },
+        ]} />
+        {stepComponent}
+      </Card>
+    </Content>
+  );
+};
+
+export default TableList;
diff --git a/react-ui/src/pages/Tool/Gen/import.tsx b/react-ui/src/pages/Tool/Gen/import.tsx
new file mode 100644
index 0000000..6e2c8df
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/import.tsx
@@ -0,0 +1,107 @@
+import { Button, Card, message, Layout } from 'antd';
+import React, { useState } from 'react';
+import { history, FormattedMessage } from '@umijs/max';
+import { importTables, queryTableList } from './service';
+import type { GenCodeType } from './data.d';
+import { ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, RollbackOutlined } from '@ant-design/icons';
+
+const { Content } = Layout;
+
+const handleImport = async (tables: string) => {
+  const hide = message.loading('正在配置');
+  try {
+    await importTables(tables);
+    hide();
+    message.success('配置成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+const ImportTableList: React.FC = () => {
+  const [selectTables, setSelectTables] = useState<string[]>([]);
+
+  const columns: ProColumns<GenCodeType>[] = [
+    {
+      title: '表名称',
+      dataIndex: 'tableName',
+    },
+    {
+      title: '表描述',
+      dataIndex: 'tableComment',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+  ];
+
+  return (
+    <Content>
+      <Card bordered={false}>
+        <ProTable<GenCodeType>
+          headerTitle="代码生成信息"
+          rowKey="tableName"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="primary"
+              onClick={async () => {
+                if (selectTables.length < 1) {
+                  message.error('请选择要导入的表!');
+                  return;
+                }
+                const success = await handleImport(selectTables.join(','));
+                if (success) {
+                  history.back();
+                }
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="gen.submit" defaultMessage="提交" />
+            </Button>,
+            <Button
+              type="primary"
+              key="goback"
+              onClick={() => {
+                history.back();
+              }}
+            >
+              <RollbackOutlined /> <FormattedMessage id="gen.goback" defaultMessage="返回" />
+            </Button>,
+          ]}
+          request={(params) =>
+            queryTableList({ ...params }).then((res) => {
+              return {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              if (selectedRows && selectedRows.length > 0) {
+                const tables = selectedRows.map((row) => {
+                  return row.tableName;
+                });
+                setSelectTables(tables);
+              }
+            },
+          }}
+        />
+      </Card>
+    </Content>
+  );
+};
+
+export default ImportTableList;
diff --git a/react-ui/src/pages/Tool/Gen/index.tsx b/react-ui/src/pages/Tool/Gen/index.tsx
new file mode 100644
index 0000000..9db00d7
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/index.tsx
@@ -0,0 +1,360 @@
+import { DownloadOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, message, Drawer, Modal, Card, Layout } from 'antd';
+import type { FormInstance } from 'antd';
+import React, { useState, useRef } from 'react';
+import { history, FormattedMessage, useAccess } from '@umijs/max';
+import PreviewForm from './components/PreviewCode';
+import type { GenCodeTableListParams, GenCodeType } from './data.d';
+import {
+  batchGenCode,
+  genCode,
+  previewCode,
+  getGenCodeList,
+  removeData,
+  syncDbInfo,
+} from './service';
+import {
+  ActionType,
+  FooterToolbar,
+  ProColumns,
+  ProDescriptions,
+  ProDescriptionsItemProps,
+  ProTable,
+} from '@ant-design/pro-components';
+
+const { Content } = Layout;
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: GenCodeType[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    await removeData({
+      ids: selectedRows.map((row) => row.tableId),
+    });
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: GenCodeType) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.tableId];
+    await removeData({
+      ids: params,
+    });
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const GenCodeView: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [showDetail, setShowDetail] = useState<boolean>(false);
+  const [showPreview, setShowPreview] = useState<boolean>(false);
+  const [preivewData, setPreivewData] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<GenCodeType>();
+  const [selectedRows, setSelectedRows] = useState<GenCodeType[]>([]);
+
+  const access = useAccess();
+
+  const columns: ProColumns<GenCodeType>[] = [
+    {
+      title: '编号',
+      dataIndex: 'tableId',
+      tip: '编号',
+      render: (dom, entity) => {
+        return (
+          <a
+            onClick={() => {
+              setCurrentRow(entity);
+              setShowDetail(true);
+            }}
+          >
+            {dom}
+          </a>
+        );
+      },
+    },
+    {
+      title: '表名',
+      dataIndex: 'tableName',
+      valueType: 'textarea',
+    },
+    {
+      title: '表描述',
+      dataIndex: 'tableComment',
+      hideInForm: true,
+      hideInSearch: true,
+    },
+    {
+      title: '实体',
+      dataIndex: 'className',
+      valueType: 'textarea',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: '更新时间',
+      dataIndex: 'updateTime',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: '操作',
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="preview"
+          hidden={!access.hasPerms('tool:gen:edit')}
+          onClick={() => {
+            previewCode(record.tableId).then((res) => {
+              if (res.code === 200) {
+                setPreivewData(res.data);
+                setShowPreview(true);
+              } else {
+                message.error('获取数据失败');
+              }
+            });
+          }}
+        >
+          预览
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          key="config"
+          hidden={!access.hasPerms('tool:gen:edit')}
+          onClick={() => {
+            history.push(`/tool/gen/edit?id=${record.tableId}`);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="delete"
+          hidden={!access.hasPerms('tool:gen:del')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除任务',
+              content: '确定删除该任务吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          key="sync"
+          hidden={!access.hasPerms('tool:gen:edit')}
+          onClick={() => {
+            syncDbInfo(record.tableName).then((res) => {
+              if (res.code === 200) {
+                message.success('同步成功');
+              } else {
+                message.error('同步失败');
+              }
+            });
+          }}
+        >
+          同步
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          key="gencode"
+          hidden={!access.hasPerms('tool:gen:edit')}
+          onClick={() => {
+            if (record.genType === '1') {
+              genCode(record.tableName).then((res) => {
+                if (res.code === 200) {
+                  message.success(`成功生成到自定义路径:${record.genPath}`);
+                } else {
+                  message.error(res.msg);
+                }
+              });
+            } else {
+              batchGenCode(record.tableName);
+            }
+          }}
+        >
+          生成代码
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <Content>
+      <Card bordered={false}>
+        <ProTable<GenCodeType>
+          headerTitle="代码生成信息"
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="tableId"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="gen"
+              hidden={!access.hasPerms('tool:gen:edit')}
+              onClick={() => {
+                if (selectedRows.length === 0) {
+                  message.error('请选择要生成的数据');
+                  return;
+                }
+                const tableNames = selectedRows.map((row) => row.tableName);
+                if (selectedRows[0].genType === '1') {
+                  genCode(tableNames.join(',')).then((res) => {
+                    if (res.code === 200) {
+                      message.success(`成功生成到自定义路径:${selectedRows[0].genPath}`);
+                    } else {
+                      message.error(res.msg);
+                    }
+                  });
+                } else {
+                  batchGenCode(tableNames.join(','));
+                }
+              }}
+            >
+              <DownloadOutlined /> <FormattedMessage id="gen.gencode" defaultMessage="生成" />
+            </Button>,
+            <Button
+              type="primary"
+              key="import"
+              hidden={!access.hasPerms('tool:gen:add')}
+              onClick={() => {
+                history.push('/tool/gen/import');
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="gen.import" defaultMessage="导入" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getGenCodeList({ ...params } as GenCodeTableListParams).then((res) => {
+              return {
+                data: res.rows,
+                total: res.rows.length,
+                success: true,
+              };
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+        {selectedRows?.length > 0 && (
+          <FooterToolbar
+            extra={
+              <div>
+                <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />{' '}
+                <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>{' '}
+                <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+              </div>
+            }
+          >
+            <Button
+              key="delete"
+              hidden={!access.hasPerms('tool:gen:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '删除任务',
+                  content: '确定删除该任务吗?',
+                  okText: '确认',
+                  cancelText: '取消',
+                  onOk: async () => {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                });
+              }}
+            >
+              <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+            </Button>
+          </FooterToolbar>
+        )}
+        <PreviewForm
+          open={showPreview}
+          data={preivewData}
+          onHide={() => {
+            setShowPreview(false);
+          }}
+        />
+        <Drawer
+          width={600}
+          open={showDetail}
+          onClose={() => {
+            setCurrentRow(undefined);
+            setShowDetail(false);
+          }}
+          closable={false}
+        >
+          {currentRow?.tableName && (
+            <ProDescriptions<GenCodeType>
+              column={2}
+              title={currentRow?.tableName}
+              request={async () => ({
+                data: currentRow || {},
+              })}
+              params={{
+                id: currentRow?.tableName,
+              }}
+              columns={columns as ProDescriptionsItemProps<GenCodeType>[]}
+            />
+          )}
+        </Drawer>
+      </Card>
+    </Content>
+  );
+};
+
+export default GenCodeView;
diff --git a/react-ui/src/pages/Tool/Gen/locales/zh-CN.ts b/react-ui/src/pages/Tool/Gen/locales/zh-CN.ts
new file mode 100644
index 0000000..9c3a5d4
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/locales/zh-CN.ts
@@ -0,0 +1,8 @@
+export default {
+  'gen.import': '导入',
+  'gen.title': '表信息',
+  'gen.goback': '返回',
+  'gen.submit': '提交',
+  'gen.gencode': '生成',
+  'gen.preview': '预览',
+};
diff --git a/react-ui/src/pages/Tool/Gen/service.ts b/react-ui/src/pages/Tool/Gen/service.ts
new file mode 100644
index 0000000..0e472ee
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/service.ts
@@ -0,0 +1,106 @@
+import { request } from '@umijs/max';
+import { downLoadZip } from '@/utils/downloadfile';
+import type { GenCodeType, GenCodeTableListParams } from './data.d';
+
+// 查询分页列表
+export async function getGenCodeList(params?: GenCodeTableListParams) {
+  const queryString = new URLSearchParams(params).toString();
+  return request(`/api/code/gen/list?${queryString}`, {
+    data: params,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 查询表信息
+export async function getGenCode(id?: string) {
+  return request(`/api/code/gen/${id}`, {
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 查询数据表信息
+export async function queryTableList(params?: any) {
+  const queryString = new URLSearchParams(params).toString();
+  return request(`/api/code/gen/db/list?${queryString}`, {
+    data: params,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 导入数据表信息
+export async function importTables(tables?: string) {
+  return request(`/api/code/gen/importTable?tables=${tables}`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 删除
+export async function removeData(params: { ids: string[] }) {
+  return request(`/api/code/gen/${params.ids}`, {
+    method: 'delete',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 添加数据
+export async function addData(params: GenCodeType) {
+  return request('/api/code/gen', {
+    method: 'POST',
+    data: {
+      ...params,
+    },
+  });
+}
+
+// 更新数据
+export async function updateData(params: GenCodeType) {
+  return request('/api/code/gen', {
+    method: 'PUT',
+    data: {
+      ...params,
+    },
+  });
+}
+
+// 更新状态
+export async function syncDbInfo(tableName: string) {
+  return request(`/api/code/gen/synchDb/${tableName}`, {
+    method: 'GET',
+  });
+}
+
+// 生成代码(自定义路径)
+export async function genCode(tableName: string) {
+  return request(`/api/code/gen/genCode/${tableName}`, {
+    method: 'GET',
+  });
+}
+
+// 生成代码(压缩包)
+export async function batchGenCode(tableName: string) {
+  return downLoadZip(`/api/code/gen/batchGenCode?tables=${tableName}`);
+}
+
+// 预览
+export async function previewCode(id: string) {
+  return request(`/api/code/gen/preview/${id}`, {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
diff --git a/react-ui/src/pages/Tool/Gen/style.less b/react-ui/src/pages/Tool/Gen/style.less
new file mode 100644
index 0000000..6999d7a
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/style.less
@@ -0,0 +1,4 @@
+.steps:global(.ant-steps) {
+  max-width: 750px;
+  margin: 16px auto;
+}
diff --git a/react-ui/src/pages/Tool/Swagger/index.tsx b/react-ui/src/pages/Tool/Swagger/index.tsx
new file mode 100644
index 0000000..adcc968
--- /dev/null
+++ b/react-ui/src/pages/Tool/Swagger/index.tsx
@@ -0,0 +1,31 @@
+import React, { useEffect } from 'react';
+
+/**
+ *
+ * @author whiteshader@163.com
+ *
+ * */
+
+const CacheInfo: React.FC = () => {
+  useEffect(() => {
+    const frame = document.getElementById('bdIframe');
+    if (frame) {
+      const deviceWidth = document.documentElement.clientWidth;
+      const deviceHeight = document.documentElement.clientHeight;
+      frame.style.width = `${Number(deviceWidth) - 260}px`;
+      frame.style.height = `${Number(deviceHeight) - 120}px`;
+    }
+  });
+
+  return (
+    <div style={{}}>
+      <iframe
+        style={{ width: '100%', border: '0px', height: '100%' }}
+        src={`/api/swagger-ui/index.html`}
+        id="bdIframe"
+      />
+    </div>
+  );
+};
+
+export default CacheInfo;
diff --git a/react-ui/src/pages/User/Center/Center.less b/react-ui/src/pages/User/Center/Center.less
new file mode 100644
index 0000000..430d88e
--- /dev/null
+++ b/react-ui/src/pages/User/Center/Center.less
@@ -0,0 +1,61 @@
+
+.avatarHolder {
+  margin-bottom: 16px;
+  text-align: center;
+  position: relative;
+  display: inline-block;
+  height: 120px;
+  
+  & > img {
+    width: 120px;
+    height: 120px;
+    margin-bottom: 20px;
+    border-radius: 50%;
+  }
+  &:hover:after {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    color: #eee;
+    font-size: 24px;
+    font-style: normal;
+    line-height: 110px;
+    background: rgba(0, 0, 0, 0.5);
+    border-radius: 50%;
+    cursor: pointer;
+    content: '+';
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+  }
+}
+
+.teamTitle {
+  margin-bottom: 12px;
+  color: @heading-color;
+  font-weight: 500;
+}
+
+.team {
+  :global {
+    .ant-avatar {
+      margin-right: 12px;
+    }
+  }
+
+  a {
+    display: block;
+    margin-bottom: 24px;
+    overflow: hidden;
+    color: @text-color;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    transition: color 0.3s;
+
+    &:hover {
+      color: @primary-color;
+    }
+  }
+}
diff --git a/react-ui/src/pages/User/Center/components/AvatarCropper/cropper.css b/react-ui/src/pages/User/Center/components/AvatarCropper/cropper.css
new file mode 100644
index 0000000..7f2f350
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/AvatarCropper/cropper.css
@@ -0,0 +1,309 @@
+/*!
+ * Cropper.js v1.5.13
+ * https://fengyuanchen.github.io/cropperjs
+ *
+ * Copyright 2015-present Chen Fengyuan
+ * Released under the MIT license
+ *
+ * Date: 2022-11-20T05:30:43.444Z
+ */
+
+.cropper-container {
+  direction: ltr;
+  font-size: 0;
+  line-height: 0;
+  position: relative;
+  -ms-touch-action: none;
+      touch-action: none;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+}
+
+.cropper-container img {
+    -webkit-backface-visibility: hidden;
+            backface-visibility: hidden;
+    display: block;
+    height: 100%;
+    image-orientation: 0deg;
+    max-height: none !important;
+    max-width: none !important;
+    min-height: 0 !important;
+    min-width: 0 !important;
+    width: 100%;
+  }
+
+.cropper-wrap-box,
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+  bottom: 0;
+  left: 0;
+  position: absolute;
+  right: 0;
+  top: 0;
+}
+
+.cropper-wrap-box,
+.cropper-canvas {
+  overflow: hidden;
+}
+
+.cropper-drag-box {
+  background-color: #fff;
+  opacity: 0;
+}
+
+.cropper-modal {
+  background-color: #000;
+  opacity: 0.5;
+}
+
+.cropper-view-box {
+  display: block;
+  height: 100%;
+  outline: 1px solid #39f;
+  outline-color: rgba(51, 153, 255, 75%);
+  overflow: hidden;
+  width: 100%;
+}
+
+.cropper-dashed {
+  border: 0 dashed #eee;
+  display: block;
+  opacity: 0.5;
+  position: absolute;
+}
+
+.cropper-dashed.dashed-h {
+    border-bottom-width: 1px;
+    border-top-width: 1px;
+    height: calc(100% / 3);
+    left: 0;
+    top: calc(100% / 3);
+    width: 100%;
+  }
+
+.cropper-dashed.dashed-v {
+    border-left-width: 1px;
+    border-right-width: 1px;
+    height: 100%;
+    left: calc(100% / 3);
+    top: 0;
+    width: calc(100% / 3);
+  }
+
+.cropper-center {
+  display: block;
+  height: 0;
+  left: 50%;
+  opacity: 0.75;
+  position: absolute;
+  top: 50%;
+  width: 0;
+}
+
+.cropper-center::before,
+  .cropper-center::after {
+    background-color: #eee;
+    content: " ";
+    display: block;
+    position: absolute;
+  }
+
+.cropper-center::before {
+    height: 1px;
+    left: -3px;
+    top: 0;
+    width: 7px;
+  }
+
+.cropper-center::after {
+    height: 7px;
+    left: 0;
+    top: -3px;
+    width: 1px;
+  }
+
+.cropper-face,
+.cropper-line,
+.cropper-point {
+  display: block;
+  height: 100%;
+  opacity: 0.1;
+  position: absolute;
+  width: 100%;
+}
+
+.cropper-face {
+  background-color: #fff;
+  left: 0;
+  top: 0;
+}
+
+.cropper-line {
+  background-color: #39f;
+}
+
+.cropper-line.line-e {
+    cursor: ew-resize;
+    right: -3px;
+    top: 0;
+    width: 5px;
+  }
+
+.cropper-line.line-n {
+    cursor: ns-resize;
+    height: 5px;
+    left: 0;
+    top: -3px;
+  }
+
+.cropper-line.line-w {
+    cursor: ew-resize;
+    left: -3px;
+    top: 0;
+    width: 5px;
+  }
+
+.cropper-line.line-s {
+    bottom: -3px;
+    cursor: ns-resize;
+    height: 5px;
+    left: 0;
+  }
+
+.cropper-point {
+  background-color: #39f;
+  height: 5px;
+  opacity: 0.75;
+  width: 5px;
+}
+
+.cropper-point.point-e {
+    cursor: ew-resize;
+    margin-top: -3px;
+    right: -3px;
+    top: 50%;
+  }
+
+.cropper-point.point-n {
+    cursor: ns-resize;
+    left: 50%;
+    margin-left: -3px;
+    top: -3px;
+  }
+
+.cropper-point.point-w {
+    cursor: ew-resize;
+    left: -3px;
+    margin-top: -3px;
+    top: 50%;
+  }
+
+.cropper-point.point-s {
+    bottom: -3px;
+    cursor: s-resize;
+    left: 50%;
+    margin-left: -3px;
+  }
+
+.cropper-point.point-ne {
+    cursor: nesw-resize;
+    right: -3px;
+    top: -3px;
+  }
+
+.cropper-point.point-nw {
+    cursor: nwse-resize;
+    left: -3px;
+    top: -3px;
+  }
+
+.cropper-point.point-sw {
+    bottom: -3px;
+    cursor: nesw-resize;
+    left: -3px;
+  }
+
+.cropper-point.point-se {
+    bottom: -3px;
+    cursor: nwse-resize;
+    height: 20px;
+    opacity: 1;
+    right: -3px;
+    width: 20px;
+  }
+
+@media (min-width: 768px) {
+
+.cropper-point.point-se {
+      height: 15px;
+      width: 15px;
+  }
+    }
+
+@media (min-width: 992px) {
+
+.cropper-point.point-se {
+      height: 10px;
+      width: 10px;
+  }
+    }
+
+@media (min-width: 1200px) {
+
+.cropper-point.point-se {
+      height: 5px;
+      opacity: 0.75;
+      width: 5px;
+  }
+    }
+
+.cropper-point.point-se::before {
+    background-color: #39f;
+    bottom: -50%;
+    content: " ";
+    display: block;
+    height: 200%;
+    opacity: 0;
+    position: absolute;
+    right: -50%;
+    width: 200%;
+  }
+
+.cropper-invisible {
+  opacity: 0;
+}
+
+.cropper-bg {
+  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
+}
+
+.cropper-hide {
+  display: block;
+  height: 0;
+  position: absolute;
+  width: 0;
+}
+
+.cropper-hidden {
+  display: none !important;
+}
+
+.cropper-move {
+  cursor: move;
+}
+
+.cropper-crop {
+  cursor: crosshair;
+}
+
+.cropper-disabled .cropper-drag-box,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+  cursor: not-allowed;
+}
diff --git a/react-ui/src/pages/User/Center/components/AvatarCropper/images/bg.png b/react-ui/src/pages/User/Center/components/AvatarCropper/images/bg.png
new file mode 100644
index 0000000..3c7056b
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/AvatarCropper/images/bg.png
Binary files differ
diff --git a/react-ui/src/pages/User/Center/components/AvatarCropper/index.less b/react-ui/src/pages/User/Center/components/AvatarCropper/index.less
new file mode 100644
index 0000000..dc3fecf
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/AvatarCropper/index.less
@@ -0,0 +1,10 @@
+.avatarPreview {
+	position: absolute;
+	top: 50%;
+	transform: translate(50%, -50%);
+	width: 200px;
+	height: 200px;
+	border-radius: 50%;
+	box-shadow: 0 0 4px #ccc;
+	overflow: hidden; 
+}
\ No newline at end of file
diff --git a/react-ui/src/pages/User/Center/components/AvatarCropper/index.tsx b/react-ui/src/pages/User/Center/components/AvatarCropper/index.tsx
new file mode 100644
index 0000000..83d0bcf
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/AvatarCropper/index.tsx
@@ -0,0 +1,144 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Modal, Row, Col, Button, Space, Upload, message } from 'antd';
+import { useIntl } from '@umijs/max';
+import { uploadAvatar } from '@/services/system/user';
+import { Cropper } from 'react-cropper';
+import './cropper.css';
+import styles from './index.less';
+import {
+  MinusOutlined,
+  PlusOutlined,
+  RedoOutlined,
+  UndoOutlined,
+  UploadOutlined,
+} from '@ant-design/icons';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2022/02/24
+ *
+ * */
+
+export type AvatarCropperProps = {
+  onFinished: (isSuccess: boolean) => void;
+  open: boolean;
+  data: any;
+};
+
+const AvatarCropperForm: React.FC<AvatarCropperProps> = (props) => {
+  const cropperRef = useRef<HTMLImageElement>(null);
+  const [avatarData, setAvatarData] = useState<any>();
+  const [previewData, setPreviewData] = useState();
+
+  useEffect(() => {
+    setAvatarData(props.data);
+  }, [props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.getCroppedCanvas().toBlob((blob: Blob) => {
+      const formData = new FormData();
+      formData.append('avatarfile', blob);
+      uploadAvatar(formData).then((res) => {
+        if (res.code === 200) {
+          message.success(res.msg);          
+          props.onFinished(true);
+        } else {
+          message.warning(res.msg);
+        }
+      });
+    }, 'image/png');
+  };
+  const handleCancel = () => {
+    props.onFinished(false);
+  };
+  const onCrop = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    setPreviewData(cropper.getCroppedCanvas().toDataURL());
+  };
+  const onRotateRight = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.rotate(90);
+  };
+  const onRotateLeft = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.rotate(-90);
+  };
+  const onZoomIn = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.zoom(0.1);
+  };
+  const onZoomOut = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.zoom(-0.1);
+  };
+  const beforeUpload = (file: any) => {
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = () => {
+      setAvatarData(reader.result);
+    };
+  };
+  return (
+    <Modal
+      width={800}
+      title={intl.formatMessage({
+        id: 'system.user.modify_avatar',
+        defaultMessage: '修改头像',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Row gutter={[16, 16]}>
+        <Col span={12} order={1}>
+          <Cropper
+            ref={cropperRef}
+            src={avatarData}
+            style={{ height: 350, width: '100%', marginBottom: '16px' }}
+            initialAspectRatio={1}
+            guides={false}
+            crop={onCrop}
+            zoomable={true}
+            zoomOnWheel={true}
+            rotatable={true}
+          />
+        </Col>
+        <Col span={12} order={2}>
+          <div className={styles.avatarPreview}>
+            <img src={previewData} style={{ height: '100%', width: '100%' }} />
+          </div>
+        </Col>
+      </Row>
+      <Row gutter={[16, 16]}>
+        <Col span={6}>
+          <Upload beforeUpload={beforeUpload} maxCount={1}>
+            <Button>
+              <UploadOutlined />
+              上传
+            </Button>
+          </Upload>
+        </Col>
+        <Col>
+          <Space>
+            <Button icon={<RedoOutlined />} onClick={onRotateRight} />
+            <Button icon={<UndoOutlined />} onClick={onRotateLeft} />
+            <Button icon={<PlusOutlined />} onClick={onZoomIn} />
+            <Button icon={<MinusOutlined />} onClick={onZoomOut} />
+          </Space>
+        </Col>
+      </Row>
+    </Modal>
+  );
+};
+
+export default AvatarCropperForm;
diff --git a/react-ui/src/pages/User/Center/components/BaseInfo/index.tsx b/react-ui/src/pages/User/Center/components/BaseInfo/index.tsx
new file mode 100644
index 0000000..5cdca5f
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/BaseInfo/index.tsx
@@ -0,0 +1,119 @@
+import React from 'react';
+import { Form, message, Row } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { ProForm, ProFormRadio, ProFormText } from '@ant-design/pro-components';
+import { updateUserProfile } from '@/services/system/user';
+
+
+export type BaseInfoProps = {
+  values: Partial<API.CurrentUser> | undefined;
+};
+
+const BaseInfo: React.FC<BaseInfoProps> = (props) => {
+  const [form] = Form.useForm();
+  const intl = useIntl();
+
+  const handleFinish = async (values: Record<string, any>) => {
+    const data = { ...props.values, ...values } as API.CurrentUser;
+    const resp = await updateUserProfile(data);
+    if (resp.code === 200) {
+      message.success('修改成功');
+    } else {
+      message.warning(resp.msg);
+    }
+  };
+
+  return (
+    <>
+      <ProForm form={form} onFinish={handleFinish} initialValues={props.values}>
+        <Row>
+          <ProFormText
+            name="nickName"
+            label={intl.formatMessage({
+              id: 'system.user.nick_name',
+              defaultMessage: '用户昵称',
+            })}
+            width="xl"
+            placeholder="请输入用户昵称"
+            rules={[
+              {
+                required: true,
+                message: (
+                  <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
+                ),
+              },
+            ]}
+          />
+        </Row>
+        <Row>
+          <ProFormText
+            name="phonenumber"
+            label={intl.formatMessage({
+              id: 'system.user.phonenumber',
+              defaultMessage: '手机号码',
+            })}
+            width="xl"
+            placeholder="请输入手机号码"
+            rules={[
+              {
+                required: false,
+                message: (
+                  <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
+                ),
+              },
+            ]}
+          />
+        </Row>
+        <Row>
+          <ProFormText
+            name="email"
+            label={intl.formatMessage({
+              id: 'system.user.email',
+              defaultMessage: '邮箱',
+            })}
+            width="xl"
+            placeholder="请输入邮箱"
+            rules={[
+              {
+                type: 'email',
+                message: '无效的邮箱地址!',
+              },
+              {
+                required: false,
+                message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />,
+              },
+            ]}
+          />
+        </Row>
+        <Row>
+          <ProFormRadio.Group
+            options={[
+              {
+                label: '男',
+                value: '0',
+              },
+              {
+                label: '女',
+                value: '1',
+              },
+            ]}
+            name="sex"
+            label={intl.formatMessage({
+              id: 'system.user.sex',
+              defaultMessage: 'sex',
+            })}
+            width="xl"
+            rules={[
+              {
+                required: false,
+                message: <FormattedMessage id="请输入性别!" defaultMessage="请输入性别!" />,
+              },
+            ]}
+          />
+        </Row>
+      </ProForm>
+    </>
+  );
+};
+
+export default BaseInfo;
diff --git a/react-ui/src/pages/User/Center/components/ResetPassword/index.tsx b/react-ui/src/pages/User/Center/components/ResetPassword/index.tsx
new file mode 100644
index 0000000..26825d4
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/ResetPassword/index.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { Form, message } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { updateUserPwd } from '@/services/system/user';
+import { ProForm, ProFormText } from '@ant-design/pro-components';
+
+const ResetPassword: React.FC = () => {
+  const [form] = Form.useForm();
+  const intl = useIntl();
+
+  const handleFinish = async (values: Record<string, any>) => {
+    const resp = await updateUserPwd(values.oldPassword, values.newPassword);
+    if (resp.code === 200) {
+      message.success('密码重置成功。');
+    } else {
+      message.warning(resp.msg);
+    }
+  };
+
+  const checkPassword = (rule: any, value: string) => {
+    const login_password = form.getFieldValue('newPassword');
+    if (value === login_password) {
+      return Promise.resolve();
+    }
+    return Promise.reject(new Error('两次密码输入不一致'));
+  };
+
+  return (
+    <>
+      <ProForm form={form} onFinish={handleFinish}>       
+          <ProFormText.Password
+            name="oldPassword"
+            label={intl.formatMessage({
+              id: 'system.user.old_password',
+              defaultMessage: '旧密码',
+            })}
+            width="xl"
+            placeholder="请输入旧密码"
+            rules={[
+              {
+                required: true,
+                message: <FormattedMessage id="请输入旧密码!" defaultMessage="请输入旧密码!" />,
+              },
+            ]}
+          />
+          <ProFormText.Password
+            name="newPassword"
+            label={intl.formatMessage({
+              id: 'system.user.new_password',
+              defaultMessage: '新密码',
+            })}
+            width="xl"
+            placeholder="请输入新密码"
+            rules={[
+              {
+                required: true,
+                message: <FormattedMessage id="请输入新密码!" defaultMessage="请输入新密码!" />,
+              },
+            ]}
+          />
+          <ProFormText.Password
+            name="confirmPassword"
+            label={intl.formatMessage({
+              id: 'system.user.confirm_password',
+              defaultMessage: '确认密码',
+            })}
+            width="xl"
+            placeholder="请输入确认密码"
+            rules={[
+              {
+                required: true,
+                message: (
+                  <FormattedMessage id="请输入确认密码!" defaultMessage="请输入确认密码!" />
+                ),
+              },
+              { validator: checkPassword },
+            ]}
+          />
+      </ProForm>
+    </>
+  );
+};
+
+export default ResetPassword;
diff --git a/react-ui/src/pages/User/Center/index.tsx b/react-ui/src/pages/User/Center/index.tsx
new file mode 100644
index 0000000..2ce308c
--- /dev/null
+++ b/react-ui/src/pages/User/Center/index.tsx
@@ -0,0 +1,200 @@
+import {
+  ClusterOutlined,
+  MailOutlined,
+  TeamOutlined,
+  UserOutlined,
+  MobileOutlined,
+  ManOutlined,
+} from '@ant-design/icons';
+import { Card, Col, Divider, List, Row } from 'antd';
+import React, { useState } from 'react';
+import styles from './Center.less';
+import BaseInfo from './components/BaseInfo';
+import ResetPassword from './components/ResetPassword';
+import AvatarCropper from './components/AvatarCropper';
+import { useRequest } from '@umijs/max';
+import { getUserInfo } from '@/services/session';
+import { PageLoading } from '@ant-design/pro-components';
+
+const operationTabList = [
+  {
+    key: 'base',
+    tab: (
+      <span>
+        基本资料
+      </span>
+    ),
+  },
+  {
+    key: 'password',
+    tab: (
+      <span>
+        重置密码
+      </span>
+    ),
+  },
+];
+
+export type tabKeyType = 'base' | 'password';
+
+const Center: React.FC = () => {
+  
+  const [tabKey, setTabKey] = useState<tabKeyType>('base');
+  
+  const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false);
+  
+  //  获取用户信息
+  const { data: userInfo, loading } = useRequest(async () => {
+    return { data: await getUserInfo()};
+  });
+  if (loading) {
+    return <div>loading...</div>;
+  }
+
+  const currentUser = userInfo?.user;
+
+  //  渲染用户信息
+  const renderUserInfo = ({
+    userName,
+    phonenumber,
+    email,
+    sex,
+    dept,
+  }: Partial<API.CurrentUser>) => {
+    return (
+      <List>
+        <List.Item>
+          <div>
+            <UserOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            用户名
+          </div>
+          <div>{userName}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <ManOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            性别
+          </div>
+          <div>{sex === '1' ? '女' : '男'}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <MobileOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            电话
+          </div>
+          <div>{phonenumber}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            邮箱
+          </div>
+          <div>{email}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <ClusterOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            部门
+          </div>
+          <div>{dept?.deptName}</div>
+        </List.Item>
+      </List>
+    );
+  };
+
+  // 渲染tab切换
+  const renderChildrenByTabKey = (tabValue: tabKeyType) => {
+    if (tabValue === 'base') {
+      return <BaseInfo values={currentUser} />;
+    }
+    if (tabValue === 'password') {
+      return <ResetPassword />;
+    }
+    return null;
+  };
+
+  if (!currentUser) {
+    return <PageLoading />;
+  }
+
+  return (
+    <div>
+      <Row gutter={[16, 24]}>
+        <Col lg={8} md={24}>
+          <Card
+            title="个人信息"
+            bordered={false}
+            loading={loading}
+          >
+            {!loading && (
+              <div style={{ textAlign: "center"}}>
+                <div className={styles.avatarHolder} onClick={()=>{setCropperModalOpen(true)}}>
+                  <img alt="" src={currentUser.avatar} />
+                </div>
+                {renderUserInfo(currentUser)}
+                <Divider dashed />
+                <div className={styles.team}>
+                  <div className={styles.teamTitle}>角色</div>
+                  <Row gutter={36}>
+                    {currentUser.roles &&
+                      currentUser.roles.map((item: any) => (
+                        <Col key={item.roleId} lg={24} xl={12}>
+                          <TeamOutlined
+                            style={{
+                              marginRight: 8,
+                            }}
+                          />
+                          {item.roleName}
+                        </Col>
+                      ))}
+                  </Row>
+                </div>
+              </div>
+            )}
+          </Card>
+        </Col>
+        <Col lg={16} md={24}>
+          <Card
+            bordered={false}
+            tabList={operationTabList}
+            activeTabKey={tabKey}
+            onTabChange={(_tabKey: string) => {
+              setTabKey(_tabKey as tabKeyType);
+            }}
+          >
+            {renderChildrenByTabKey(tabKey)}
+          </Card>
+        </Col>
+      </Row>
+      <AvatarCropper
+        onFinished={() => {
+          setCropperModalOpen(false);     
+        }}
+        open={cropperModalOpen}
+        data={currentUser.avatar}
+      />
+    </div>
+  );
+};
+
+export default Center;
diff --git a/react-ui/src/pages/User/Login/index.tsx b/react-ui/src/pages/User/Login/index.tsx
new file mode 100644
index 0000000..a18f785
--- /dev/null
+++ b/react-ui/src/pages/User/Login/index.tsx
@@ -0,0 +1,436 @@
+import Footer from '@/components/Footer';
+import { getCaptchaImg, login } from '@/services/system/auth';
+import { getFakeCaptcha } from '@/services/ant-design-pro/login';
+import {
+  AlipayCircleOutlined,
+  LockOutlined,
+  MobileOutlined,
+  TaobaoCircleOutlined,
+  UserOutlined,
+  WeiboCircleOutlined,
+} from '@ant-design/icons';
+import {
+  LoginForm,
+  ProFormCaptcha,
+  ProFormCheckbox,
+  ProFormText,
+} from '@ant-design/pro-components';
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import { FormattedMessage, history, SelectLang, useIntl, useModel, Helmet } from '@umijs/max';
+import { Alert, Col, message, Row, Tabs, Image } from 'antd';
+import Settings from '../../../../config/defaultSettings';
+import React, { useEffect, useState } from 'react';
+import { flushSync } from 'react-dom';
+import { clearSessionToken, setSessionToken } from '@/access';
+
+const ActionIcons = () => {
+  const langClassName = useEmotionCss(({ token }) => {
+    return {
+      marginLeft: '8px',
+      color: 'rgba(0, 0, 0, 0.2)',
+      fontSize: '24px',
+      verticalAlign: 'middle',
+      cursor: 'pointer',
+      transition: 'color 0.3s',
+      '&:hover': {
+        color: token.colorPrimaryActive,
+      },
+    };
+  });
+
+  return (
+    <>
+      <AlipayCircleOutlined key="AlipayCircleOutlined" className={langClassName} />
+      <TaobaoCircleOutlined key="TaobaoCircleOutlined" className={langClassName} />
+      <WeiboCircleOutlined key="WeiboCircleOutlined" className={langClassName} />
+    </>
+  );
+};
+
+const Lang = () => {
+  const langClassName = useEmotionCss(({ token }) => {
+    return {
+      width: 42,
+      height: 42,
+      lineHeight: '42px',
+      position: 'fixed',
+      right: 16,
+      borderRadius: token.borderRadius,
+      ':hover': {
+        backgroundColor: token.colorBgTextHover,
+      },
+    };
+  });
+
+  return (
+    <div className={langClassName} data-lang>
+      {SelectLang && <SelectLang />}
+    </div>
+  );
+};
+
+const LoginMessage: React.FC<{
+  content: string;
+}> = ({ content }) => {
+  return (
+    <Alert
+      style={{
+        marginBottom: 24,
+      }}
+      message={content}
+      type="error"
+      showIcon
+    />
+  );
+};
+
+const Login: React.FC = () => {
+  const [userLoginState, setUserLoginState] = useState<API.LoginResult>({code: 200});
+  const [type, setType] = useState<string>('account');
+  const { initialState, setInitialState } = useModel('@@initialState');
+  const [captchaCode, setCaptchaCode] = useState<string>('');
+  const [uuid, setUuid] = useState<string>('');
+
+  const containerClassName = useEmotionCss(() => {
+    return {
+      display: 'flex',
+      flexDirection: 'column',
+      height: '100vh',
+      overflow: 'auto',
+      backgroundImage:
+        "url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
+      backgroundSize: '100% 100%',
+    };
+  });
+
+  const intl = useIntl();
+
+  const getCaptchaCode = async () => {
+    const response = await getCaptchaImg();
+    const imgdata = `data:image/png;base64,${response.img}`;
+    setCaptchaCode(imgdata);
+    setUuid(response.uuid);
+  };
+
+  const fetchUserInfo = async () => {
+    const userInfo = await initialState?.fetchUserInfo?.();
+    if (userInfo) {
+      flushSync(() => {
+        setInitialState((s) => ({
+          ...s,
+          currentUser: userInfo,
+        }));
+      });
+    }
+  };
+
+  const handleSubmit = async (values: API.LoginParams) => {
+    try {
+      // 登录
+      const response = await login({ ...values, uuid });
+      if (response.code === 200) {
+        const defaultLoginSuccessMessage = intl.formatMessage({
+          id: 'pages.login.success',
+          defaultMessage: '登录成功!',
+        });
+        const current = new Date();
+        const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60);
+        console.log('login response: ', response);
+        setSessionToken(response?.token, response?.token, expireTime);
+        message.success(defaultLoginSuccessMessage);
+        await fetchUserInfo();
+        console.log('login ok');
+        const urlParams = new URL(window.location.href).searchParams;
+        history.push(urlParams.get('redirect') || '/');
+        return;
+      } else {
+        console.log(response.msg);
+        clearSessionToken();
+        // 如果失败去设置用户错误信息
+        setUserLoginState({ ...response, type });
+        getCaptchaCode();
+      }
+    } catch (error) {
+      const defaultLoginFailureMessage = intl.formatMessage({
+        id: 'pages.login.failure',
+        defaultMessage: '登录失败,请重试!',
+      });
+      console.log(error);
+      message.error(defaultLoginFailureMessage);
+    }
+  };
+  const { code } = userLoginState;
+  const loginType = type;
+
+  useEffect(() => {
+    getCaptchaCode();
+  }, []);
+
+  return (
+    <div className={containerClassName}>
+      <Helmet>
+        <title>
+          {intl.formatMessage({
+            id: 'menu.login',
+            defaultMessage: '登录页',
+          })}
+          - {Settings.title}
+        </title>
+      </Helmet>
+      <Lang />
+      <div
+        style={{
+          flex: '1',
+          padding: '32px 0',
+        }}
+      >
+        <LoginForm
+          contentStyle={{
+            minWidth: 280,
+            maxWidth: '75vw',
+          }}
+          logo={<img alt="logo" src="/logo.svg" />}
+          title="Ant Design"
+          subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
+          initialValues={{
+            autoLogin: true,
+          }}
+          actions={[
+            <FormattedMessage
+              key="loginWith"
+              id="pages.login.loginWith"
+              defaultMessage="其他登录方式"
+            />,
+            <ActionIcons key="icons" />,
+          ]}
+          onFinish={async (values) => {
+            await handleSubmit(values as API.LoginParams);
+          }}
+        >
+          <Tabs
+            activeKey={type}
+            onChange={setType}
+            centered
+            items={[
+              {
+                key: 'account',
+                label: intl.formatMessage({
+                  id: 'pages.login.accountLogin.tab',
+                  defaultMessage: '账户密码登录',
+                }),
+              },
+              {
+                key: 'mobile',
+                label: intl.formatMessage({
+                  id: 'pages.login.phoneLogin.tab',
+                  defaultMessage: '手机号登录',
+                }),
+              },
+            ]}
+          />
+
+          {code !== 200 && loginType === 'account' && (
+            <LoginMessage
+              content={intl.formatMessage({
+                id: 'pages.login.accountLogin.errorMessage',
+                defaultMessage: '账户或密码错误(admin/admin123)',
+              })}
+            />
+          )}
+          {type === 'account' && (
+            <>
+              <ProFormText
+                name="username"
+                initialValue="admin"
+                fieldProps={{
+                  size: 'large',
+                  prefix: <UserOutlined />,
+                }}
+                placeholder={intl.formatMessage({
+                  id: 'pages.login.username.placeholder',
+                  defaultMessage: '用户名: admin',
+                })}
+                rules={[
+                  {
+                    required: true,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.username.required"
+                        defaultMessage="请输入用户名!"
+                      />
+                    ),
+                  },
+                ]}
+              />
+              <ProFormText.Password
+                name="password"
+                initialValue="admin123"
+                fieldProps={{
+                  size: 'large',
+                  prefix: <LockOutlined />,
+                }}
+                placeholder={intl.formatMessage({
+                  id: 'pages.login.password.placeholder',
+                  defaultMessage: '密码: admin123',
+                })}
+                rules={[
+                  {
+                    required: true,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.password.required"
+                        defaultMessage="请输入密码!"
+                      />
+                    ),
+                  },
+                ]}
+              />
+              <Row>
+                <Col flex={3}>
+                  <ProFormText
+                    style={{
+                      float: 'right',
+                    }}
+                    name="code"
+                    placeholder={intl.formatMessage({
+                      id: 'pages.login.captcha.placeholder',
+                      defaultMessage: '请输入验证',
+                    })}
+                    rules={[
+                      {
+                        required: true,
+                        message: (
+                          <FormattedMessage
+                            id="pages.searchTable.updateForm.ruleName.nameRules"
+                            defaultMessage="请输入验证啊"
+                          />
+                        ),
+                      },
+                    ]}
+                  />
+                </Col>
+                <Col flex={2}>
+                  <Image
+                    src={captchaCode}
+                    alt="验证码"
+                    style={{
+                      display: 'inline-block',
+                      verticalAlign: 'top',
+                      cursor: 'pointer',
+                      paddingLeft: '10px',
+                      width: '100px',
+                    }}
+                    preview={false}
+                    onClick={() => getCaptchaCode()}
+                  />
+                </Col>
+              </Row>
+            </>
+          )}
+
+          {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
+          {type === 'mobile' && (
+            <>
+              <ProFormText
+                fieldProps={{
+                  size: 'large',
+                  prefix: <MobileOutlined />,
+                }}
+                name="mobile"
+                placeholder={intl.formatMessage({
+                  id: 'pages.login.phoneNumber.placeholder',
+                  defaultMessage: '手机号',
+                })}
+                rules={[
+                  {
+                    required: true,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.phoneNumber.required"
+                        defaultMessage="请输入手机号!"
+                      />
+                    ),
+                  },
+                  {
+                    pattern: /^1\d{10}$/,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.phoneNumber.invalid"
+                        defaultMessage="手机号格式错误!"
+                      />
+                    ),
+                  },
+                ]}
+              />
+              <ProFormCaptcha
+                fieldProps={{
+                  size: 'large',
+                  prefix: <LockOutlined />,
+                }}
+                captchaProps={{
+                  size: 'large',
+                }}
+                placeholder={intl.formatMessage({
+                  id: 'pages.login.captcha.placeholder',
+                  defaultMessage: '请输入验证码',
+                })}
+                captchaTextRender={(timing, count) => {
+                  if (timing) {
+                    return `${count} ${intl.formatMessage({
+                      id: 'pages.getCaptchaSecondText',
+                      defaultMessage: '获取验证码',
+                    })}`;
+                  }
+                  return intl.formatMessage({
+                    id: 'pages.login.phoneLogin.getVerificationCode',
+                    defaultMessage: '获取验证码',
+                  });
+                }}
+                name="captcha"
+                rules={[
+                  {
+                    required: true,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.captcha.required"
+                        defaultMessage="请输入验证码!"
+                      />
+                    ),
+                  },
+                ]}
+                onGetCaptcha={async (phone) => {
+                  const result = await getFakeCaptcha({
+                    phone,
+                  });
+                  if (!result) {
+                    return;
+                  }
+                  message.success('获取验证码成功!验证码为:1234');
+                }}
+              />
+            </>
+          )}
+          <div
+            style={{
+              marginBottom: 24,
+            }}
+          >
+            <ProFormCheckbox noStyle name="autoLogin">
+              <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
+            </ProFormCheckbox>
+            <a
+              style={{
+                float: 'right',
+              }}
+            >
+              <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
+            </a>
+          </div>
+        </LoginForm>
+      </div>
+      <Footer />
+    </div>
+  );
+};
+
+export default Login;
diff --git a/react-ui/src/pages/User/Settings/index.tsx b/react-ui/src/pages/User/Settings/index.tsx
new file mode 100644
index 0000000..f29d0b9
--- /dev/null
+++ b/react-ui/src/pages/User/Settings/index.tsx
@@ -0,0 +1,20 @@
+import { PageContainer } from '@ant-design/pro-components';
+import { Card } from 'antd';
+import React from 'react';
+
+/**
+ *
+ * @author whiteshader@163.com
+ *
+ * */
+
+
+const Settings: React.FC = () => {
+  return (
+    <PageContainer>
+      <Card title="Developing" />
+    </PageContainer>
+  );
+};
+
+export default Settings;
diff --git a/react-ui/src/pages/Welcome.tsx b/react-ui/src/pages/Welcome.tsx
new file mode 100644
index 0000000..d0c49f7
--- /dev/null
+++ b/react-ui/src/pages/Welcome.tsx
@@ -0,0 +1,164 @@
+import { PageContainer } from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Card, theme } from 'antd';
+import React from 'react';
+
+/**
+ * 每个单独的卡片,为了复用样式抽成了组件
+ * @param param0
+ * @returns
+ */
+const InfoCard: React.FC<{
+  title: string;
+  index: number;
+  desc: string;
+  href: string;
+}> = ({ title, href, index, desc }) => {
+  const { useToken } = theme;
+
+  const { token } = useToken();
+
+  return (
+    <div
+      style={{
+        backgroundColor: token.colorBgContainer,
+        boxShadow: token.boxShadow,
+        borderRadius: '8px',
+        fontSize: '14px',
+        color: token.colorTextSecondary,
+        lineHeight: '22px',
+        padding: '16px 19px',
+        minWidth: '220px',
+        flex: 1,
+      }}
+    >
+      <div
+        style={{
+          display: 'flex',
+          gap: '4px',
+          alignItems: 'center',
+        }}
+      >
+        <div
+          style={{
+            width: 48,
+            height: 48,
+            lineHeight: '22px',
+            backgroundSize: '100%',
+            textAlign: 'center',
+            padding: '8px 16px 16px 12px',
+            color: '#FFF',
+            fontWeight: 'bold',
+            backgroundImage:
+              "url('https://gw.alipayobjects.com/zos/bmw-prod/daaf8d50-8e6d-4251-905d-676a24ddfa12.svg')",
+          }}
+        >
+          {index}
+        </div>
+        <div
+          style={{
+            fontSize: '16px',
+            color: token.colorText,
+            paddingBottom: 8,
+          }}
+        >
+          {title}
+        </div>
+      </div>
+      <div
+        style={{
+          fontSize: '14px',
+          color: token.colorTextSecondary,
+          textAlign: 'justify',
+          lineHeight: '22px',
+          marginBottom: 8,
+        }}
+      >
+        {desc}
+      </div>
+      <a href={href} target="_blank" rel="noreferrer">
+        了解更多 {'>'}
+      </a>
+    </div>
+  );
+};
+
+const Welcome: React.FC = () => {
+  const { token } = theme.useToken();
+  const { initialState } = useModel('@@initialState');
+  return (
+    <PageContainer>
+      <Card
+        style={{
+          borderRadius: 8,
+        }}
+        bodyStyle={{
+          backgroundImage:
+            initialState?.settings?.navTheme === 'realDark'
+              ? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
+              : 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
+        }}
+      >
+        <div
+          style={{
+            backgroundPosition: '100% -30%',
+            backgroundRepeat: 'no-repeat',
+            backgroundSize: '274px auto',
+            backgroundImage:
+              "url('https://gw.alipayobjects.com/mdn/rms_a9745b/afts/img/A*BuFmQqsB2iAAAAAAAAAAAAAAARQnAQ')",
+          }}
+        >
+          <div
+            style={{
+              fontSize: '20px',
+              color: token.colorTextHeading,
+            }}
+          >
+            欢迎使用 Ant Design Pro
+          </div>
+          <p
+            style={{
+              fontSize: '14px',
+              color: token.colorTextSecondary,
+              lineHeight: '22px',
+              marginTop: 16,
+              marginBottom: 32,
+              width: '65%',
+            }}
+          >
+            Ant Design Pro 是一个整合了 umi,Ant Design 和 ProComponents
+            的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
+          </p>
+          <div
+            style={{
+              display: 'flex',
+              flexWrap: 'wrap',
+              gap: 16,
+            }}
+          >
+            <InfoCard
+              index={1}
+              href="https://umijs.org/docs/introduce/introduce"
+              title="了解 umi"
+              desc="umi 是一个可扩展的企业级前端应用框架,umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。"
+            />
+            <InfoCard
+              index={2}
+              title="了解 ant design"
+              href="https://ant.design"
+              desc="antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。"
+            />
+            <InfoCard
+              index={3}
+              title="了解 Pro Components"
+              href="https://procomponents.ant.design"
+              desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
+            />
+          </div>
+        </div>
+      </Card>
+    </PageContainer>
+  );
+};
+
+export default Welcome;
diff --git a/react-ui/src/requestErrorConfig.ts b/react-ui/src/requestErrorConfig.ts
new file mode 100644
index 0000000..c0c4a6a
--- /dev/null
+++ b/react-ui/src/requestErrorConfig.ts
@@ -0,0 +1,109 @@
+import type { RequestOptions } from '@@/plugin-request/request';
+import type { RequestConfig } from '@umijs/max';
+import { message, notification } from 'antd';
+
+// 错误处理方案: 错误类型
+enum ErrorShowType {
+  SILENT = 0,
+  WARN_MESSAGE = 1,
+  ERROR_MESSAGE = 2,
+  NOTIFICATION = 3,
+  REDIRECT = 9,
+}
+// 与后端约定的响应数据格式
+interface ResponseStructure {
+  success: boolean;
+  data: any;
+  errorCode?: number;
+  errorMessage?: string;
+  showType?: ErrorShowType;
+}
+
+/**
+ * @name 错误处理
+ * pro 自带的错误处理, 可以在这里做自己的改动
+ * @doc https://umijs.org/docs/max/request#配置
+ */
+export const errorConfig: RequestConfig = {
+  // 错误处理: umi@3 的错误处理方案。
+  errorConfig: {
+    // 错误抛出
+    errorThrower: (res) => {
+      const { success, data, errorCode, errorMessage, showType } =
+        res as unknown as ResponseStructure;
+      if (!success) {
+        const error: any = new Error(errorMessage);
+        error.name = 'BizError';
+        error.info = { errorCode, errorMessage, showType, data };
+        throw error; // 抛出自制的错误
+      }
+    },
+    // 错误接收及处理
+    errorHandler: (error: any, opts: any) => {
+      if (opts?.skipErrorHandler) throw error;
+      // 我们的 errorThrower 抛出的错误。
+      if (error.name === 'BizError') {
+        const errorInfo: ResponseStructure | undefined = error.info;
+        if (errorInfo) {
+          const { errorMessage, errorCode } = errorInfo;
+          switch (errorInfo.showType) {
+            case ErrorShowType.SILENT:
+              // do nothing
+              break;
+            case ErrorShowType.WARN_MESSAGE:
+              message.warning(errorMessage);
+              break;
+            case ErrorShowType.ERROR_MESSAGE:
+              message.error(errorMessage);
+              break;
+            case ErrorShowType.NOTIFICATION:
+              notification.open({
+                description: errorMessage,
+                message: errorCode,
+              });
+              break;
+            case ErrorShowType.REDIRECT:
+              // TODO: redirect
+              break;
+            default:
+              message.error(errorMessage);
+          }
+        }
+      } else if (error.response) {
+        // Axios 的错误
+        // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
+        message.error(`Response status:${error.response.status}`);
+      } else if (error.request) {
+        // 请求已经成功发起,但没有收到响应
+        // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
+        // 而在node.js中是 http.ClientRequest 的实例
+        message.error('None response! Please retry.');
+      } else {
+        // 发送请求时出了点问题
+        message.error('Request error, please retry.');
+      }
+    },
+  },
+
+  // 请求拦截器
+  requestInterceptors: [
+    (config: RequestOptions) => {
+      // 拦截请求配置,进行个性化处理。
+      const url = config?.url?.concat('?token = 123');
+      return { ...config, url };
+    },
+  ],
+
+  // 响应拦截器
+  responseInterceptors: [
+    (response) => {
+      // 拦截响应数据,进行个性化处理
+      const { data } = response as unknown as ResponseStructure;
+
+      if (data?.success === false) {
+        message.error('请求失败!');
+      }
+      return response;
+    },
+  ],
+};
diff --git a/react-ui/src/service-worker.js b/react-ui/src/service-worker.js
new file mode 100644
index 0000000..b86726c
--- /dev/null
+++ b/react-ui/src/service-worker.js
@@ -0,0 +1,65 @@
+/* eslint-disable no-restricted-globals */
+/* eslint-disable no-underscore-dangle */
+/* globals workbox */
+workbox.core.setCacheNameDetails({
+  prefix: 'antd-pro',
+  suffix: 'v5',
+});
+// Control all opened tabs ASAP
+workbox.clientsClaim();
+
+/**
+ * Use precaching list generated by workbox in build process.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
+ */
+workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
+
+/**
+ * Register a navigation route.
+ * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
+ */
+workbox.routing.registerNavigationRoute('/index.html');
+
+/**
+ * Use runtime cache:
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
+ *
+ * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
+ */
+
+/** Handle API requests */
+workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
+
+/** Handle third party requests */
+workbox.routing.registerRoute(
+  /^https:\/\/gw\.alipayobjects\.com\//,
+  workbox.strategies.networkFirst(),
+);
+workbox.routing.registerRoute(
+  /^https:\/\/cdnjs\.cloudflare\.com\//,
+  workbox.strategies.networkFirst(),
+);
+workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
+
+/** Response to client after skipping waiting with MessageChannel */
+addEventListener('message', (event) => {
+  const replyPort = event.ports[0];
+  const message = event.data;
+  if (replyPort && message && message.type === 'skip-waiting') {
+    event.waitUntil(
+      self.skipWaiting().then(
+        () => {
+          replyPort.postMessage({
+            error: null,
+          });
+        },
+        (error) => {
+          replyPort.postMessage({
+            error,
+          });
+        },
+      ),
+    );
+  }
+});
diff --git a/react-ui/src/services/ant-design-pro/api.ts b/react-ui/src/services/ant-design-pro/api.ts
new file mode 100644
index 0000000..64a950a
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/api.ts
@@ -0,0 +1,11 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 此处后端没有提供注释 GET /api/notices */
+export async function getNotices(options?: { [key: string]: any }) {
+  return request<API.NoticeIconList>('/api/notices', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/ant-design-pro/index.ts b/react-ui/src/services/ant-design-pro/index.ts
new file mode 100644
index 0000000..9ae58be
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/index.ts
@@ -0,0 +1,12 @@
+// @ts-ignore
+/* eslint-disable */
+// API 更新时间:
+// API 唯一标识:
+import * as api from './api';
+import * as login from './login';
+import * as rule from './rule';
+export default {
+  api,
+  login,
+  rule,
+};
diff --git a/react-ui/src/services/ant-design-pro/login.ts b/react-ui/src/services/ant-design-pro/login.ts
new file mode 100644
index 0000000..3b00b43
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/login.ts
@@ -0,0 +1,38 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 登录接口 POST /api/login/account */
+export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
+  return request<API.LoginResult>('/api/login/account', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** 发送验证码 POST /api/login/captcha */
+export async function getFakeCaptcha(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.getFakeCaptchaParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.FakeCaptcha>('/api/login/captcha', {
+    method: 'POST',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
+
+/** 登录接口 POST /api/login/outLogin */
+export async function outLogin(options?: { [key: string]: any }) {
+  return request<Record<string, any>>('/api/login/outLogin', {
+    method: 'POST',
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/ant-design-pro/rule.ts b/react-ui/src/services/ant-design-pro/rule.ts
new file mode 100644
index 0000000..4b8ebc5
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/rule.ts
@@ -0,0 +1,42 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 获取规则列表 GET /api/rule */
+export async function rule(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.ruleParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.RuleList>('/api/rule', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
+
+/** 新建规则 PUT /api/rule */
+export async function updateRule(options?: { [key: string]: any }) {
+  return request<API.RuleListItem>('/api/rule', {
+    method: 'PUT',
+    ...(options || {}),
+  });
+}
+
+/** 新建规则 POST /api/rule */
+export async function addRule(options?: { [key: string]: any }) {
+  return request<API.RuleListItem>('/api/rule', {
+    method: 'POST',
+    ...(options || {}),
+  });
+}
+
+/** 删除规则 DELETE /api/rule */
+export async function removeRule(options?: { [key: string]: any }) {
+  return request<Record<string, any>>('/api/rule', {
+    method: 'DELETE',
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/ant-design-pro/typings.d.ts b/react-ui/src/services/ant-design-pro/typings.d.ts
new file mode 100644
index 0000000..4ee1264
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/typings.d.ts
@@ -0,0 +1,108 @@
+declare namespace API {
+  type CurrentUser = UserInfo & {
+    signature?: string;
+    title?: string;
+    group?: string;
+    tags?: { key?: string; label?: string }[];
+    notifyCount?: number;
+    unreadCount?: number;
+    country?: string;
+    access?: string;
+    geographic?: {
+      province?: { label?: string; key?: string };
+      city?: { label?: string; key?: string };
+    };
+    address?: string;
+    phone?: string;
+  };
+
+  type ErrorResponse = {
+    /** 业务约定的错误码 */
+    errorCode: string;
+    /** 业务上的错误信息 */
+    errorMessage?: string;
+    /** 业务上的请求是否成功 */
+    success?: boolean;
+  };
+
+  type FakeCaptcha = {
+    code?: number;
+    status?: string;
+  };
+
+  type getFakeCaptchaParams = {
+    /** 手机号 */
+    phone?: string;
+  };
+
+  type LoginParams = {
+    username?: string;
+    password?: string;
+    uuid?: string;
+    autoLogin?: boolean;
+    type?: string;
+  };
+
+  type LoginResult = {
+    code: number;
+    msg?: string;
+    type?: string;
+    token?: string;
+  };
+
+  type NoticeIconItem = {
+    id?: string;
+    extra?: string;
+    key?: string;
+    read?: boolean;
+    avatar?: string;
+    title?: string;
+    status?: string;
+    datetime?: string;
+    description?: string;
+    type?: NoticeIconItemType;
+  };
+
+  type NoticeIconItemType = 'notification' | 'message' | 'event';
+
+  type NoticeIconList = {
+    data?: NoticeIconItem[];
+    /** 列表的内容总数 */
+    total?: number;
+    success?: boolean;
+  };
+
+  type PageParams = {
+    current?: number;
+    pageSize?: number;
+  };
+
+  type RuleList = {
+    data?: RuleListItem[];
+    /** 列表的内容总数 */
+    total?: number;
+    success?: boolean;
+  };
+
+  type RuleListItem = {
+    key?: number;
+    disabled?: boolean;
+    href?: string;
+    avatar?: string;
+    name?: string;
+    owner?: string;
+    desc?: string;
+    callNo?: number;
+    status?: number;
+    updatedAt?: string;
+    createdAt?: string;
+    progress?: number;
+  };
+
+  type ruleParams = {
+    /** 当前的页码 */
+    current?: number;
+    /** 页面的容量 */
+    pageSize?: number;
+  };
+}
diff --git a/react-ui/src/services/monitor/cache.ts b/react-ui/src/services/monitor/cache.ts
new file mode 100644
index 0000000..244368e
--- /dev/null
+++ b/react-ui/src/services/monitor/cache.ts
@@ -0,0 +1,17 @@
+import { request } from '@umijs/max'; 
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+
+// 获取服务器信息
+export async function getCacheInfo() {
+  return request<API.Monitor.CacheInfoResult>('/api/monitor/cache', {
+    method: 'GET',
+  });
+}
diff --git a/react-ui/src/services/monitor/cachelist.ts b/react-ui/src/services/monitor/cachelist.ts
new file mode 100644
index 0000000..d348d4b
--- /dev/null
+++ b/react-ui/src/services/monitor/cachelist.ts
@@ -0,0 +1,51 @@
+import { request } from '@umijs/max'; 
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2022/06/27
+ * 
+ * */
+ 
+
+// 查询缓存名称列表
+export function listCacheName() {
+  return request<API.Monitor.CacheNamesResponse>('/api/monitor/cache/getNames', {
+    method: 'get'
+  })
+}
+
+// 查询缓存键名列表
+export function listCacheKey(cacheName: string) {
+  return request<API.Monitor.CacheKeysResponse>('/api/monitor/cache/getKeys/' + cacheName, {
+    method: 'get'
+  })
+}
+
+// 查询缓存内容
+export function getCacheValue(cacheName: string, cacheKey: string) {
+  return request<API.Monitor.CacheValueResponse>('/api/monitor/cache/getValue/' + cacheName + '/' + cacheKey, {
+    method: 'get'
+  })
+}
+
+// 清理指定名称缓存
+export function clearCacheName(cacheName: string) {
+  return request<API.Result>('/api/monitor/cache/clearCacheName/' + cacheName, {
+    method: 'delete'
+  })
+}
+
+// 清理指定键名缓存
+export function clearCacheKey(cacheKey: string) {
+  return request<API.Result>('/api/monitor/cache/clearCacheKey/' + cacheKey, {
+    method: 'delete'
+  })
+}
+
+// 清理全部缓存
+export function clearCacheAll() {
+  return request<API.Result>('/api/monitor/cache/clearCacheAll', {
+    method: 'delete'
+  })
+}
diff --git a/react-ui/src/services/monitor/job.ts b/react-ui/src/services/monitor/job.ts
new file mode 100644
index 0000000..192f819
--- /dev/null
+++ b/react-ui/src/services/monitor/job.ts
@@ -0,0 +1,73 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/**
+ * 定时任务调度 API
+ *
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+// 查询定时任务调度列表
+export async function getJobList(params?: API.Monitor.JobListParams) {
+  return request<API.Monitor.JobPageResult>('/api/monitor/job/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询定时任务调度详细
+export function getJob(jobId: number) {
+  return request<API.Monitor.JobInfoResult>(`/api/monitor/job/${jobId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增定时任务调度
+export async function addJob(params: API.Monitor.Job) {
+  return request<API.Result>('/api/monitor/job', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改定时任务调度
+export async function updateJob(params: API.Monitor.Job) {
+  return request<API.Result>('/api/monitor/job', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除定时任务调度
+export async function removeJob(ids: string) {
+  return request<API.Result>(`/api/monitor/job/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出定时任务调度
+export function exportJob(params?: API.Monitor.JobListParams) {
+  return downLoadXlsx(`/api/monitor/job/export`, { params }, `job_${new Date().getTime()}.xlsx`);
+}
+
+// 定时任务立即执行一次
+export async function runJob(jobId: number, jobGroup: string) {
+  const job = {
+    jobId,
+    jobGroup,
+  };
+  return request('/api/monitor/job/run', {
+    method: 'PUT',
+    data: job,
+  });
+}
diff --git a/react-ui/src/services/monitor/jobLog.ts b/react-ui/src/services/monitor/jobLog.ts
new file mode 100644
index 0000000..9885aa7
--- /dev/null
+++ b/react-ui/src/services/monitor/jobLog.ts
@@ -0,0 +1,40 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/**
+ * 定时任务调度日志 API
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+// 查询定时任务调度日志列表
+export async function getJobLogList(params?: API.Monitor.JobLogListParams) {
+  return request<API.Monitor.JobLogPageResult>('/api/schedule/job/log/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+
+// 删除定时任务调度日志
+export async function removeJobLog(jobLogId: string) {
+  return request<API.Result>(`/api/schedule/job/log/${jobLogId}`, {
+    method: 'DELETE'
+  });
+}
+
+// 清空调度日志
+export function cleanJobLog() {
+  return request('/api/schedule/job/log/clean', {
+    method: 'delete'
+  })
+}
+
+// 导出定时任务调度日志
+export function exportJobLog(params?: API.Monitor.JobLogListParams) {
+  return downLoadXlsx(`/api/monitor/jobLog/export`, { params }, `joblog_${new Date().getTime()}.xlsx`);
+}
diff --git a/react-ui/src/services/monitor/logininfor.ts b/react-ui/src/services/monitor/logininfor.ts
new file mode 100644
index 0000000..ea1864a
--- /dev/null
+++ b/react-ui/src/services/monitor/logininfor.ts
@@ -0,0 +1,68 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询系统访问记录列表
+export async function getLogininforList(params?: API.Monitor.LogininforListParams) {
+  return request<API.Monitor.LogininforPageResult>('/api/monitor/logininfor/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询系统访问记录详细
+export function getLogininfor(infoId: number) {
+  return request<API.Monitor.LogininforInfoResult>(`/api/monitor/logininfor/${infoId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增系统访问记录
+export async function addLogininfor(params: API.Monitor.Logininfor) {
+  return request<API.Result>('/api/monitor/logininfor', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改系统访问记录
+export async function updateLogininfor(params: API.Monitor.Logininfor) {
+  return request<API.Result>('/api/monitor/logininfor', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除系统访问记录
+export async function removeLogininfor(ids: string) {
+  return request<API.Result>(`/api/monitor/logininfor/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出系统访问记录
+export function exportLogininfor(params?: API.Monitor.LogininforListParams) {
+  return downLoadXlsx(`/api/monitor/logininfor/export`, { params }, `logininfor_${new Date().getTime()}.xlsx`);
+}
+
+// 解锁用户登录状态
+export function unlockLogininfor(userName: string) {
+  return request<API.Result>('/api/monitor/logininfor/unlock/' + userName, {
+    method: 'get'
+  })
+}
+
+// 清空登录日志
+export function cleanLogininfor() {
+  return request<API.Result>('/api/monitor/logininfor/clean', {
+    method: 'delete'
+  })
+}
diff --git a/react-ui/src/services/monitor/online.ts b/react-ui/src/services/monitor/online.ts
new file mode 100644
index 0000000..ae2a6ac
--- /dev/null
+++ b/react-ui/src/services/monitor/online.ts
@@ -0,0 +1,23 @@
+import { request } from '@umijs/max';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ *
+ * */
+
+// 查询在线用户列表
+export async function getOnlineUserList(params?: API.Monitor.OnlineUserListParams) {
+  return request<API.Monitor.OnlineUserPageResult>('/api/monitor/online/list', {
+    method: 'GET',
+    params,
+  });
+}
+
+// 强退用户
+export async function forceLogout(tokenId: string) {
+  return request(`/api/monitor/online/${tokenId}`, {
+    method: 'DELETE',
+  });
+}
diff --git a/react-ui/src/services/monitor/operlog.ts b/react-ui/src/services/monitor/operlog.ts
new file mode 100644
index 0000000..004863a
--- /dev/null
+++ b/react-ui/src/services/monitor/operlog.ts
@@ -0,0 +1,60 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询操作日志记录列表
+export async function getOperlogList(params?: API.Monitor.OperlogListParams) {
+  return request<API.Monitor.OperlogPageResult>('/api/monitor/operlog/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询操作日志记录详细
+export function getOperlog(operId: number) {
+  return request<API.Monitor.OperlogInfoResult>(`/api/monitor/operlog/${operId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增操作日志记录
+export async function addOperlog(params: API.Monitor.Operlog) {
+  return request<API.Result>('/api/monitor/operlog', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改操作日志记录
+export async function updateOperlog(params: API.Monitor.Operlog) {
+  return request<API.Result>('/api/monitor/operlog', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除操作日志记录
+export async function removeOperlog(ids: string) {
+  return request<API.Result>(`/api/monitor/operlog/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+export async function cleanAllOperlog() {
+  return request<API.Result>(`/api/monitor/operlog/clean`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出操作日志记录
+export function exportOperlog(params?: API.Monitor.OperlogListParams) {
+  return downLoadXlsx(`/api/monitor/operlog/export`, { params }, `operlog_${new Date().getTime()}.xlsx`);
+}
diff --git a/react-ui/src/services/monitor/server.ts b/react-ui/src/services/monitor/server.ts
new file mode 100644
index 0000000..24a849f
--- /dev/null
+++ b/react-ui/src/services/monitor/server.ts
@@ -0,0 +1,16 @@
+import { request } from '@umijs/max'; 
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+
+// 获取服务器信息
+export async function getServerInfo() {
+  return request('/api/monitor/server', {
+    method: 'GET',
+  });
+}
diff --git a/react-ui/src/services/session.ts b/react-ui/src/services/session.ts
new file mode 100644
index 0000000..8b4e0d3
--- /dev/null
+++ b/react-ui/src/services/session.ts
@@ -0,0 +1,159 @@
+import { createIcon } from '@/utils/IconUtil';
+import { MenuDataItem } from '@ant-design/pro-components';
+import { request } from '@umijs/max';
+import React, { lazy } from 'react';
+
+
+let remoteMenu: any = null;
+
+export function getRemoteMenu() {
+  return remoteMenu;
+}
+
+export function setRemoteMenu(data: any) {
+  remoteMenu = data;
+}
+
+
+function patchRouteItems(route: any, menu: any, parentPath: string) {
+  for (const menuItem of menu) {
+    if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') {
+      if (menuItem.routes) {
+        let hasItem = false;
+        let newItem = null;
+        for (const routeChild of route.routes) {
+          if (routeChild.path === menuItem.path) {
+            hasItem = true;
+            newItem = routeChild;
+          }
+        }
+        if (!hasItem) {
+          newItem = {
+            path: menuItem.path,
+            routes: [],
+            children: []
+          }
+          route.routes.push(newItem)
+        }
+        patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/');
+      }
+    } else {
+      const names: string[] = menuItem.component.split('/');
+      let path = '';
+      names.forEach(name => {
+        if (path.length > 0) {
+          path += '/';
+        }
+        if (name !== 'index') {
+          path += name.at(0)?.toUpperCase() + name.substr(1);
+        } else {
+          path += name;
+        }
+      })
+      if (!path.endsWith('.tsx')) {
+        path += '.tsx'
+      }
+      if (route.routes === undefined) {
+        route.routes = [];
+      }
+      if (route.children === undefined) {
+        route.children = [];
+      }
+      const newRoute = {
+        element: React.createElement(lazy(() => import('@/pages/' + path))),
+        path: parentPath + menuItem.path,
+      }
+      route.children.push(newRoute);
+      route.routes.push(newRoute);
+    }
+  }
+}
+
+export function patchRouteWithRemoteMenus(routes: any) {
+  if (remoteMenu === null) { return; }
+  let proLayout = null;
+  for (const routeItem of routes) {
+    if (routeItem.id === 'ant-design-pro-layout') {
+      proLayout = routeItem;
+      break;
+    }
+  }
+  patchRouteItems(proLayout, remoteMenu, '');
+}
+
+/** 获取当前的用户 GET /api/getUserInfo */
+export async function getUserInfo(options?: Record<string, any>) {
+  return request<API.UserInfoResult>('/api/getInfo', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
+
+// 刷新方法
+export async function refreshToken() {
+  return request('/api/auth/refresh', {
+    method: 'post'
+  })
+}
+
+export async function getRouters(): Promise<any> {
+  return request('/api/getRouters');
+}
+
+export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] {
+  return childrens.map((item: API.RoutersMenuItem) => {
+    return {
+      path: item.path,
+      icon: createIcon(item.meta.icon),
+      //  icon: item.meta.icon,
+      name: item.meta.title,
+      routes: item.children ? convertCompatRouters(item.children) : undefined,
+      hideChildrenInMenu: item.hidden,
+      hideInMenu: item.hidden,
+      component: item.component,
+      authority: item.perms,
+    };
+  });
+}
+
+export async function getRoutersInfo(): Promise<MenuDataItem[]> {
+  return getRouters().then((res) => {
+    if (res.code === 200) {
+      return convertCompatRouters(res.data);
+    } else {
+      return [];
+    }
+  });
+}
+
+export function getMatchMenuItem(
+  path: string,
+  menuData: MenuDataItem[] | undefined,
+): MenuDataItem[] {
+  if (!menuData) return [];
+  let items: MenuDataItem[] = [];
+  menuData.forEach((item) => {
+    if (item.path) {
+      if (item.path === path) {
+        items.push(item);
+        return;
+      }
+      if (path.length >= item.path?.length) {
+        const exp = `${item.path}/*`;
+        if (path.match(exp)) {
+          if (item.routes) {
+            const subpath = path.substr(item.path.length + 1);
+            const subItem: MenuDataItem[] = getMatchMenuItem(subpath, item.routes);
+            items = items.concat(subItem);
+          } else {
+            const paths = path.split('/');
+            if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') {
+              items.push(item);
+            }
+          }
+        }
+      }
+    }
+  });
+  return items;
+}
diff --git a/react-ui/src/services/swagger/index.ts b/react-ui/src/services/swagger/index.ts
new file mode 100644
index 0000000..83cf97c
--- /dev/null
+++ b/react-ui/src/services/swagger/index.ts
@@ -0,0 +1,12 @@
+// @ts-ignore
+/* eslint-disable */
+// API 更新时间:
+// API 唯一标识:
+import * as pet from './pet';
+import * as store from './store';
+import * as user from './user';
+export default {
+  pet,
+  store,
+  user,
+};
diff --git a/react-ui/src/services/swagger/pet.ts b/react-ui/src/services/swagger/pet.ts
new file mode 100644
index 0000000..b887475
--- /dev/null
+++ b/react-ui/src/services/swagger/pet.ts
@@ -0,0 +1,153 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Update an existing pet PUT /pet */
+export async function updatePet(body: API.Pet, options?: { [key: string]: any }) {
+  return request<any>('/pet', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Add a new pet to the store POST /pet */
+export async function addPet(body: API.Pet, options?: { [key: string]: any }) {
+  return request<any>('/pet', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Find pet by ID Returns a single pet GET /pet/${param0} */
+export async function getPetById(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.getPetByIdParams,
+  options?: { [key: string]: any },
+) {
+  const { petId: param0, ...queryParams } = params;
+  return request<API.Pet>(`/pet/${param0}`, {
+    method: 'GET',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** Updates a pet in the store with form data POST /pet/${param0} */
+export async function updatePetWithForm(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.updatePetWithFormParams,
+  body: { name?: string; status?: string },
+  options?: { [key: string]: any },
+) {
+  const { petId: param0, ...queryParams } = params;
+  const formData = new FormData();
+
+  Object.keys(body).forEach((ele) => {
+    const item = (body as any)[ele];
+
+    if (item !== undefined && item !== null) {
+      formData.append(
+        ele,
+        typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
+      );
+    }
+  });
+
+  return request<any>(`/pet/${param0}`, {
+    method: 'POST',
+    params: { ...queryParams },
+    data: formData,
+    ...(options || {}),
+  });
+}
+
+/** Deletes a pet DELETE /pet/${param0} */
+export async function deletePet(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.deletePetParams & {
+    // header
+    api_key?: string;
+  },
+  options?: { [key: string]: any },
+) {
+  const { petId: param0, ...queryParams } = params;
+  return request<any>(`/pet/${param0}`, {
+    method: 'DELETE',
+    headers: {},
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** uploads an image POST /pet/${param0}/uploadImage */
+export async function uploadFile(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.uploadFileParams,
+  body: { additionalMetadata?: string; file?: string },
+  file?: File,
+  options?: { [key: string]: any },
+) {
+  const { petId: param0, ...queryParams } = params;
+  const formData = new FormData();
+
+  if (file) {
+    formData.append('file', file);
+  }
+
+  Object.keys(body).forEach((ele) => {
+    const item = (body as any)[ele];
+
+    if (item !== undefined && item !== null) {
+      formData.append(
+        ele,
+        typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
+      );
+    }
+  });
+
+  return request<API.ApiResponse>(`/pet/${param0}/uploadImage`, {
+    method: 'POST',
+    params: { ...queryParams },
+    data: formData,
+    requestType: 'form',
+    ...(options || {}),
+  });
+}
+
+/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
+export async function findPetsByStatus(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.findPetsByStatusParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.Pet[]>('/pet/findByStatus', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
+
+/** Finds Pets by tags Muliple tags can be provided with comma separated strings. Use         tag1, tag2, tag3 for testing. GET /pet/findByTags */
+export async function findPetsByTags(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.findPetsByTagsParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.Pet[]>('/pet/findByTags', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/swagger/store.ts b/react-ui/src/services/swagger/store.ts
new file mode 100644
index 0000000..b9c689a
--- /dev/null
+++ b/react-ui/src/services/swagger/store.ts
@@ -0,0 +1,48 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
+export async function getInventory(options?: { [key: string]: any }) {
+  return request<Record<string, any>>('/store/inventory', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
+
+/** Place an order for a pet POST /store/order */
+export async function placeOrder(body: API.Order, options?: { [key: string]: any }) {
+  return request<API.Order>('/store/order', {
+    method: 'POST',
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10.         Other values will generated exceptions GET /store/order/${param0} */
+export async function getOrderById(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.getOrderByIdParams,
+  options?: { [key: string]: any },
+) {
+  const { orderId: param0, ...queryParams } = params;
+  return request<API.Order>(`/store/order/${param0}`, {
+    method: 'GET',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** Delete purchase order by ID For valid response try integer IDs with positive integer value.         Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
+export async function deleteOrder(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.deleteOrderParams,
+  options?: { [key: string]: any },
+) {
+  const { orderId: param0, ...queryParams } = params;
+  return request<any>(`/store/order/${param0}`, {
+    method: 'DELETE',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/swagger/typings.d.ts b/react-ui/src/services/swagger/typings.d.ts
new file mode 100644
index 0000000..d06bcfc
--- /dev/null
+++ b/react-ui/src/services/swagger/typings.d.ts
@@ -0,0 +1,112 @@
+declare namespace API {
+  type ApiResponse = {
+    code?: number;
+    type?: string;
+    message?: string;
+  };
+
+  type Category = {
+    id?: number;
+    name?: string;
+  };
+
+  type deleteOrderParams = {
+    /** ID of the order that needs to be deleted */
+    orderId: number;
+  };
+
+  type deletePetParams = {
+    api_key?: string;
+    /** Pet id to delete */
+    petId: number;
+  };
+
+  type deleteUserParams = {
+    /** The name that needs to be deleted */
+    username: string;
+  };
+
+  type findPetsByStatusParams = {
+    /** Status values that need to be considered for filter */
+    status: ('available' | 'pending' | 'sold')[];
+  };
+
+  type findPetsByTagsParams = {
+    /** Tags to filter by */
+    tags: string[];
+  };
+
+  type getOrderByIdParams = {
+    /** ID of pet that needs to be fetched */
+    orderId: number;
+  };
+
+  type getPetByIdParams = {
+    /** ID of pet to return */
+    petId: number;
+  };
+
+  type getUserByNameParams = {
+    /** The name that needs to be fetched. Use user1 for testing.  */
+    username: string;
+  };
+
+  type loginUserParams = {
+    /** The user name for login */
+    username: string;
+    /** The password for login in clear text */
+    password: string;
+  };
+
+  type Order = {
+    id?: number;
+    petId?: number;
+    quantity?: number;
+    shipDate?: string;
+    /** Order Status */
+    status?: 'placed' | 'approved' | 'delivered';
+    complete?: boolean;
+  };
+
+  type Pet = {
+    id?: number;
+    category?: Category;
+    name: string;
+    photoUrls: string[];
+    tags?: Tag[];
+    /** pet status in the store */
+    status?: 'available' | 'pending' | 'sold';
+  };
+
+  type Tag = {
+    id?: number;
+    name?: string;
+  };
+
+  type updatePetWithFormParams = {
+    /** ID of pet that needs to be updated */
+    petId: number;
+  };
+
+  type updateUserParams = {
+    /** name that need to be updated */
+    username: string;
+  };
+
+  type uploadFileParams = {
+    /** ID of pet to update */
+    petId: number;
+  };
+
+  type User = {
+    id?: number;
+    username?: string;
+    firstName?: string;
+    lastName?: string;
+    email?: string;
+    password?: string;
+    phone?: string;
+    /** User Status */
+    userStatus?: number;
+  };
+}
diff --git a/react-ui/src/services/swagger/user.ts b/react-ui/src/services/swagger/user.ts
new file mode 100644
index 0000000..4dd6f42
--- /dev/null
+++ b/react-ui/src/services/swagger/user.ts
@@ -0,0 +1,100 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Create user This can only be done by the logged in user. POST /user */
+export async function createUser(body: API.User, options?: { [key: string]: any }) {
+  return request<any>('/user', {
+    method: 'POST',
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Get user by user name GET /user/${param0} */
+export async function getUserByName(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.getUserByNameParams,
+  options?: { [key: string]: any },
+) {
+  const { username: param0, ...queryParams } = params;
+  return request<API.User>(`/user/${param0}`, {
+    method: 'GET',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
+export async function updateUser(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.updateUserParams,
+  body: API.User,
+  options?: { [key: string]: any },
+) {
+  const { username: param0, ...queryParams } = params;
+  return request<any>(`/user/${param0}`, {
+    method: 'PUT',
+    params: { ...queryParams },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
+export async function deleteUser(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.deleteUserParams,
+  options?: { [key: string]: any },
+) {
+  const { username: param0, ...queryParams } = params;
+  return request<any>(`/user/${param0}`, {
+    method: 'DELETE',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** Creates list of users with given input array POST /user/createWithArray */
+export async function createUsersWithArrayInput(
+  body: API.User[],
+  options?: { [key: string]: any },
+) {
+  return request<any>('/user/createWithArray', {
+    method: 'POST',
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Creates list of users with given input array POST /user/createWithList */
+export async function createUsersWithListInput(body: API.User[], options?: { [key: string]: any }) {
+  return request<any>('/user/createWithList', {
+    method: 'POST',
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Logs user into the system GET /user/login */
+export async function loginUser(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.loginUserParams,
+  options?: { [key: string]: any },
+) {
+  return request<string>('/user/login', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
+
+/** Logs out current logged in user session GET /user/logout */
+export async function logoutUser(options?: { [key: string]: any }) {
+  return request<any>('/user/logout', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/system/auth.ts b/react-ui/src/services/system/auth.ts
new file mode 100644
index 0000000..b757618
--- /dev/null
+++ b/react-ui/src/services/system/auth.ts
@@ -0,0 +1,39 @@
+import { request } from '@umijs/max';
+
+export async function getCaptchaImg(params?: Record<string, any>, options?: Record<string, any>) {
+  return request('/api/captchaImage', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    headers: {
+      isToken: false,
+    },
+    ...(options || {}),
+  });
+}
+
+/** 登录接口 POST /api/login/account */
+export async function login(body: API.LoginParams, options?: Record<string, any>) {
+  return request<API.LoginResult>('/api/login', {
+    method: 'POST',
+    headers: {
+      isToken: false,
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** 退出登录接口 POST /api/login/outLogin */
+export async function logout() {
+  return request<Record<string, any>>('/api/logout', {
+    method: 'delete',
+  });
+}
+
+// 获取手机验证码
+export async function getMobileCaptcha(mobile: string) {
+  return request(`/api/login/captcha?mobile=${mobile}`);
+}
diff --git a/react-ui/src/services/system/config.ts b/react-ui/src/services/system/config.ts
new file mode 100644
index 0000000..9843daf
--- /dev/null
+++ b/react-ui/src/services/system/config.ts
@@ -0,0 +1,62 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询参数配置列表
+export async function getConfigList(params?: API.System.ConfigListParams) {
+  return request<API.System.ConfigPageResult>('/api/system/config/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询参数配置详细
+export function getConfig(configId: number) {
+  return request<API.System.ConfigInfoResult>(`/api/system/config/${configId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增参数配置
+export async function addConfig(params: API.System.Config) {
+  return request<API.Result>('/api/system/config', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改参数配置
+export async function updateConfig(params: API.System.Config) {
+  return request<API.Result>('/api/system/config', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除参数配置
+export async function removeConfig(ids: string) {
+  return request<API.Result>(`/api/system/config/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出参数配置
+export function exportConfig(params?: API.System.ConfigListParams) {
+  return downLoadXlsx(`/api/system/config/export`, { params }, `config_${new Date().getTime()}.xlsx`);
+}
+
+
+// 刷新参数缓存
+export function refreshConfigCache() {
+  return request<API.Result>('/api/system/config/refreshCache', {
+    method: 'delete'
+  })
+}
diff --git a/react-ui/src/services/system/dept.ts b/react-ui/src/services/system/dept.ts
new file mode 100644
index 0000000..ad58807
--- /dev/null
+++ b/react-ui/src/services/system/dept.ts
@@ -0,0 +1,56 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询部门列表
+export async function getDeptList(params?: API.System.DeptListParams) {
+  return request<API.System.DeptPageResult>('/api/system/dept/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询部门列表(排除节点)
+export function getDeptListExcludeChild(deptId: number) {
+  return request(`/api/system/dept/list/exclude/${deptId}`, {
+    method: 'get',
+  });
+}
+
+// 查询部门详细
+export function getDept(deptId: number) {
+  return request<API.System.DeptInfoResult>(`/api/system/dept/${deptId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增部门
+export async function addDept(params: API.System.Dept) {
+  return request<API.Result>('/api/system/dept', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改部门
+export async function updateDept(params: API.System.Dept) {
+  return request<API.Result>('/api/system/dept', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除部门
+export async function removeDept(ids: string) {
+  return request<API.Result>(`/api/system/dept/${ids}`, {
+    method: 'DELETE'
+  });
+}
diff --git a/react-ui/src/services/system/dict.ts b/react-ui/src/services/system/dict.ts
new file mode 100644
index 0000000..5671124
--- /dev/null
+++ b/react-ui/src/services/system/dict.ts
@@ -0,0 +1,120 @@
+import { request } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { HttpResult } from '@/enums/httpEnum';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ *
+ * */
+
+// 查询字典类型列表
+export async function getDictTypeList(params?: API.DictTypeListParams) {
+  return request(`/api/system/dict/type/list`, {
+    params: {
+      ...params,
+    },
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 查询字典类型详细
+export function getDictType(dictId: string) {
+  return request(`/api/system/dict/type/${dictId}`, {
+    method: 'GET',
+  });
+}
+
+// 查询字典数据详细
+export async function getDictValueEnum(dictType: string, isDigital?: boolean): Promise<DictValueEnumObj> {
+  const resp = await request<API.System.DictTypeResult>(`/api/system/dict/data/type/${dictType}`, {
+    method: 'GET',
+  });
+  if(resp.code === HttpResult.SUCCESS) {
+    const opts: DictValueEnumObj = {};
+    resp.data.forEach((item: any) => {
+      opts[item.dictValue] = {
+        text: item.dictLabel,
+        label: item.dictLabel,
+        value: isDigital ? Number(item.dictValue) : item.dictValue,
+        key: item.dictCode,
+        listClass: item.listClass,
+        status: item.listClass };
+    });
+    return opts;
+  } else {
+    return {};
+  }
+}
+
+export async function getDictSelectOption(dictType: string, isDigital?: boolean) {
+  const resp = await request<API.System.DictTypeResult>(`/api/system/dict/data/type/${dictType}`, {
+    method: 'GET',
+  });
+  if (resp.code === 200) {
+    const options: DictValueEnumObj[] = resp.data.map((item) => {
+      return {
+        text: item.dictLabel,
+        label: item.dictLabel,
+        value: isDigital ? Number(item.dictValue) : item.dictValue,
+        key: item.dictCode,
+        listClass: item.listClass,
+        status: item.listClass
+      };
+    });
+    return options;
+  }
+  return [];
+};
+
+// 新增字典类型
+export async function addDictType(params: API.System.DictType) {
+  return request<API.Result>('/api/system/dict/type', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改字典类型
+export async function updateDictType(params: API.System.DictType) {
+  return request<API.Result>('/api/system/dict/type', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除字典类型
+export async function removeDictType(ids: string) {
+  return request<API.Result>(`/api/system/dict/type/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出字典类型
+export function exportDictType(params?: API.System.DictTypeListParams) {
+  return downLoadXlsx(`/api/system/dict/type/export`, { params }, `dict_type_${new Date().getTime()}.xlsx`);
+}
+
+// 获取字典选择框列表
+export async function getDictTypeOptionSelect(params?: API.DictTypeListParams) {
+  return request('/api/system/dict/type/optionselect', {
+    params: {
+      ...params,
+    },
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
diff --git a/react-ui/src/services/system/dictdata.ts b/react-ui/src/services/system/dictdata.ts
new file mode 100644
index 0000000..f7856e8
--- /dev/null
+++ b/react-ui/src/services/system/dictdata.ts
@@ -0,0 +1,65 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询字典数据列表
+export async function getDictDataList(
+  params?: API.System.DictDataListParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.System.DictDataPageResult>('/api/system/dict/data/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params,
+    ...(options || {}),
+  });
+}
+
+// 查询字典数据详细
+export function getDictData(dictCode: number, options?: { [key: string]: any }) {
+  return request<API.System.DictDataInfoResult>(`/api/system/dict/data/${dictCode}`, {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
+
+// 新增字典数据
+export async function addDictData(params: API.System.DictData, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/dict/data', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {}),
+  });
+}
+
+// 修改字典数据
+export async function updateDictData(params: API.System.DictData, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/dict/data', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {}),
+  });
+}
+
+// 删除字典数据
+export async function removeDictData(ids: string, options?: { [key: string]: any }) {
+  return request<API.Result>(`/api/system/dict/data/${ids}`, {
+    method: 'DELETE',
+    ...(options || {}),
+  });
+}
+
+// 导出字典数据
+export function exportDictData(
+  params?: API.System.DictDataListParams,
+  options?: { [key: string]: any },
+) {
+  return downLoadXlsx(`/api/system/dict/data/export`, { params }, `dict_data_${new Date().getTime()}.xlsx`);
+}
diff --git a/react-ui/src/services/system/index.ts b/react-ui/src/services/system/index.ts
new file mode 100644
index 0000000..24ce62d
--- /dev/null
+++ b/react-ui/src/services/system/index.ts
@@ -0,0 +1,14 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+
+import * as Auth from './auth';
+import * as User from './User';
+import * as Dict from './dict';
+import * as Menu from './menu';
+
+export default {
+  Auth,
+  User,
+  Dict,
+  Menu,
+};
diff --git a/react-ui/src/services/system/menu.ts b/react-ui/src/services/system/menu.ts
new file mode 100644
index 0000000..b2f411b
--- /dev/null
+++ b/react-ui/src/services/system/menu.ts
@@ -0,0 +1,61 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询菜单权限列表
+export async function getMenuList(params?: API.System.MenuListParams, options?: { [key: string]: any }) {
+  return request<API.System.MenuPageResult>('/api/system/menu/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params,
+    ...(options || {}),
+  });
+}
+
+// 查询菜单权限详细
+export function getMenu(menuId: number, options?: { [key: string]: any }) {
+  return request<API.System.MenuInfoResult>(`/api/system/menu/${menuId}`, {
+    method: 'GET',
+    ...(options || {})
+  });
+}
+
+// 新增菜单权限
+export async function addMenu(params: API.System.Menu, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/menu', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {})
+  });
+}
+
+// 修改菜单权限
+export async function updateMenu(params: API.System.Menu, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/menu', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {})
+  });
+}
+
+// 删除菜单权限
+export async function removeMenu(ids: string, options?: { [key: string]: any }) {
+  return request<API.Result>(`/api/system/menu/${ids}`, {
+    method: 'DELETE',
+    ...(options || {})
+  });
+}
+
+// 查询菜单权限详细
+export function getMenuTree() {
+  return request('/api/system/menu/treeselect', {
+    method: 'GET',
+  });
+}
diff --git a/react-ui/src/services/system/notice.ts b/react-ui/src/services/system/notice.ts
new file mode 100644
index 0000000..2c86a08
--- /dev/null
+++ b/react-ui/src/services/system/notice.ts
@@ -0,0 +1,49 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询通知公告列表
+export async function getNoticeList(params?: API.System.NoticeListParams) {
+  return request<API.System.NoticePageResult>('/api/system/notice/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询通知公告详细
+export function getNotice(noticeId: number) {
+  return request<API.System.NoticeInfoResult>(`/api/system/notice/${noticeId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增通知公告
+export async function addNotice(params: API.System.Notice) {
+  return request<API.Result>('/api/system/notice', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改通知公告
+export async function updateNotice(params: API.System.Notice) {
+  return request<API.Result>('/api/system/notice', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除通知公告
+export async function removeNotice(ids: string) {
+  return request<API.Result>(`/api/system/notice/${ids}`, {
+    method: 'DELETE'
+  });
+}
diff --git a/react-ui/src/services/system/post.ts b/react-ui/src/services/system/post.ts
new file mode 100644
index 0000000..3170b89
--- /dev/null
+++ b/react-ui/src/services/system/post.ts
@@ -0,0 +1,54 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询岗位信息列表
+export async function getPostList(params?: API.System.PostListParams) {
+  return request<API.System.PostPageResult>('/api/system/post/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询岗位信息详细
+export function getPost(postId: number) {
+  return request<API.System.PostInfoResult>(`/api/system/post/${postId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增岗位信息
+export async function addPost(params: API.System.Post) {
+  return request<API.Result>('/api/system/post', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改岗位信息
+export async function updatePost(params: API.System.Post) {
+  return request<API.Result>('/api/system/post', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除岗位信息
+export async function removePost(ids: string) {
+  return request<API.Result>(`/api/system/post/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出岗位信息
+export function exportPost(params?: API.System.PostListParams) {
+  return downLoadXlsx(`/api/system/post/export`, { params }, `post_${new Date().getTime()}.xlsx`);
+}
diff --git a/react-ui/src/services/system/role.ts b/react-ui/src/services/system/role.ts
new file mode 100644
index 0000000..3836243
--- /dev/null
+++ b/react-ui/src/services/system/role.ts
@@ -0,0 +1,128 @@
+import { ContentType } from '@/enums/httpEnum';
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询角色信息列表
+export async function getRoleList(params?: API.System.RoleListParams) {
+  return request<API.System.RolePageResult>('/api/system/role/list', {
+    method: 'GET',
+    headers: { 'Content-Type': ContentType.FORM_URLENCODED },
+    params
+  });
+}
+
+// 查询角色信息详细
+export function getRole(roleId: number) {
+  return request<API.System.RoleInfoResult>(`/api/system/role/${roleId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增角色信息
+export async function addRole(params: API.System.Role) {
+  return request<API.Result>('/api/system/role', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改角色信息
+export async function updateRole(params: API.System.Role) {
+  return request<API.Result>('/api/system/role', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除角色信息
+export async function removeRole(ids: string) {
+  return request<API.Result>(`/api/system/role/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出角色信息
+export function exportRole(params?: API.System.RoleListParams) {
+  return downLoadXlsx(`/api/system/role/export`, { params }, `role_${new Date().getTime()}.xlsx`);
+}
+
+// 获取角色菜单列表
+export function getRoleMenuList(id: number) {
+  return request<API.System.RoleMenuResult>(`/api/system/menu/roleMenuTreeselect/${id}`, {
+    method: 'get',
+  });
+}
+
+// 角色数据权限
+export function updateRoleDataScope(data: Record<string, any>) {
+  return request('/api/system/role/dataScope', {
+    method: 'put',
+    data
+  })
+}
+
+// 角色状态修改
+export function changeRoleStatus(roleId: number, status: string) {
+  const data = {
+    roleId,
+    status
+  }
+  return request<API.Result>('/api/system/role/changeStatus', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 查询角色已授权用户列表
+export function allocatedUserList(params?: API.System.RoleListParams) {
+  return request('/api/system/role/authUser/allocatedList', {
+    method: 'get',
+    params
+  })
+}
+
+// 查询角色未授权用户列表
+export function unallocatedUserList(params?: API.System.RoleListParams) {
+  return request('/api/system/role/authUser/unallocatedList', {
+    method: 'get',
+    params
+  })
+}
+
+// 取消用户授权角色
+export function authUserCancel(data: any) {
+  return request<API.Result>('/api/system/role/authUser/cancel', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 批量取消用户授权角色
+export function authUserCancelAll(data: any) {
+  return request<API.Result>('/api/system/role/authUser/cancelAll', {
+    method: 'put',
+    params: data
+  })
+}
+
+// 授权用户选择
+export function authUserSelectAll(data: Record<string, any>) {
+  return request<API.Result>('/api/system/role/authUser/selectAll', {
+    method: 'put',
+    params: data,
+    headers: { 'Content-Type': ContentType.FORM_URLENCODED },
+  })
+}
+
+// 根据角色ID查询部门树结构
+export function getDeptTreeSelect(roleId: number) {
+  return request('/api/system/role/deptTree/' + roleId, {
+    method: 'get'
+  })
+}
diff --git a/react-ui/src/services/system/user.ts b/react-ui/src/services/system/user.ts
new file mode 100644
index 0000000..da997bf
--- /dev/null
+++ b/react-ui/src/services/system/user.ts
@@ -0,0 +1,152 @@
+import { formatTreeData } from '@/utils/tree';
+import { request } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询用户信息列表
+export async function getUserList(params?: API.System.UserListParams, options?: { [key: string]: any }) {
+  return request<API.System.UserPageResult>('/api/system/user/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params,
+    ...(options || {})
+  });
+}
+
+// 查询用户信息详细
+export function getUser(userId: number, options?: { [key: string]: any }) {
+  return request<API.System.UserInfoResult>(`/api/system/user/${userId}`, {
+    method: 'GET',
+    ...(options || {})
+  });
+}
+
+// 新增用户信息
+export async function addUser(params: API.System.User, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/user', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {})
+  });
+}
+
+// 修改用户信息
+export async function updateUser(params: API.System.User, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/user', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {})
+  });
+}
+
+// 删除用户信息
+export async function removeUser(ids: string, options?: { [key: string]: any }) {
+  return request<API.Result>(`/api/system/user/${ids}`, {
+    method: 'DELETE',
+    ...(options || {})
+  });
+}
+
+// 导出用户信息
+export function exportUser(params?: API.System.UserListParams, options?: { [key: string]: any }) {
+  return downLoadXlsx(`/api/system/user/export`, { params }, `user_${new Date().getTime()}.xlsx`);
+}
+
+// 用户状态修改
+export function changeUserStatus(userId: number, status: string) {
+  const data = {
+    userId,
+    status
+  }
+  return request<API.Result>('/api/system/user/changeStatus', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 查询用户个人信息
+export function getUserProfile() {
+  return request('/api/system/user/profile', {
+    method: 'get'
+  })
+}
+
+export function updateUserProfile(data: API.CurrentUser) {
+  return request<API.Result>('/api/system/user/profile', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 用户密码重置
+export function resetUserPwd(userId: number, password: string) {
+  const data = {
+    userId,
+    password
+  }
+  return request<API.Result>('/api/system/user/resetPwd', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 用户t个人密码重置
+export function updateUserPwd(oldPassword: string, newPassword: string) {
+  const data = {
+    oldPassword,
+    newPassword
+  }
+  return request<API.Result>('/api/system/user/profile/updatePwd', {
+    method: 'put',
+    params: data
+  })
+}
+
+// 用户头像上传
+export function uploadAvatar(data: any) {
+  return request('/api/system/user/profile/avatar', {
+    method: 'post',
+    data: data
+  })
+}
+
+
+// 查询授权角色
+export function getAuthRole(userId: number) {
+  return request('/system/user/authRole/' + userId, {
+    method: 'get'
+  })
+}
+
+// 保存授权角色
+export function updateAuthRole(data: Record<string, any>) {
+  return request('/system/user/authRole', {
+    method: 'put',
+    params: data
+  })
+}
+
+// 获取数据列表
+export function getDeptTree(params: any): Promise<DataNode[]> {
+  return new Promise((resolve) => {
+    request(`/api/system/user/deptTree`, {
+      method: 'get',
+      params,
+    }).then((res: any) => {
+      if (res && res.code === 200) {
+        const treeData = formatTreeData(res.data);
+        resolve(treeData);
+      } else {
+        resolve([]);
+      }
+    });
+  });
+}
diff --git a/react-ui/src/services/typings.d.ts b/react-ui/src/services/typings.d.ts
new file mode 100644
index 0000000..1e0b8b5
--- /dev/null
+++ b/react-ui/src/services/typings.d.ts
@@ -0,0 +1,191 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+
+declare namespace API {
+  interface PageInfo {
+    current?: number;
+    pageSize?: number;
+    total?: number;
+    list?: Array<Record<string, any>>;
+  }
+
+  interface PageInfo_UserInfo_ {
+    current?: number;
+    pageSize?: number;
+    total?: number;
+    list?: Array<UserInfo>;
+  }
+
+  interface Result {
+    code: number;
+    msg: string;
+    data?: Record<string, any>;
+  }
+
+  interface Result_PageInfo_UserInfo__ {
+    code: number;
+    msg: string;
+    data?: PageInfo_UserInfo_;
+  }
+  interface UserInfoResult {
+    code?: number;
+    msg?: string;
+    user: UserInfo;
+    permissions: any;
+    roles: any;
+  }
+
+  interface Result_string_ {
+    success?: boolean;
+    errorMessage?: string;
+    data?: string;
+  }
+
+  type UserGenderEnum = 'MALE' | 'FEMALE';
+
+  interface UserInfo {
+    userId?: string;
+    userName?: string;
+    nickName?: string;
+    avatar?: string;
+    sex?: string;
+    email?: string;
+    gender?: UserGenderEnum;
+    unreadCount: number;
+    address?: string;
+    phonenumber?: string;
+    dept?: Dept;
+    roles?: Role[];
+    permissions: string[];
+  }
+
+  interface UserInfoVO {
+    name?: string;
+    /** nick */
+    nickName?: string;
+    /** email */
+    email?: string;
+  }
+
+  type definitions_0 = null;
+
+  type MenuItemMeta = {
+    title: string;
+    icon: string;
+    noCache: boolean;
+    link: string;
+  };
+
+  type RoutersMenuItem = {
+    alwaysShow?: boolean;
+    children?: RoutersMenuItem[];
+    component?: string;
+    hidden?: boolean;
+    meta: MenuItemMeta;
+    name: string;
+    path: string;
+    redirect?: string;
+    [key: string]: any;
+  };
+  interface GetRoutersResult {
+    code: number;
+    msg: string;
+    data: RoutersMenuItem[];
+  }
+
+  type NoticeIconList = {
+    data?: NoticeIconItem[];
+    /** 列表的内容总数 */
+    total?: number;
+    success?: boolean;
+  };
+
+  type NoticeIconItemType = 'notification' | 'message' | 'event';
+
+  type NoticeIconItem = {
+    id?: string;
+    extra?: string;
+    key?: string;
+    read?: boolean;
+    avatar?: string;
+    title?: string;
+    status?: string;
+    datetime?: string;
+    description?: string;
+    type?: NoticeIconItemType;
+  };
+
+  export type MenuType = {
+    menuId: number;
+    menuName: string;
+    parentId: string;
+    orderNum: number;
+    path: string;
+    component: string;
+    isFrame: number;
+    isCache: number;
+    menuType: string;
+    visible: string;
+    status: string;
+    perms: string;
+    icon: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  };
+
+  export type MenuListParams = {
+    menuId?: string;
+    menuName?: string;
+    parentId?: string;
+    orderNum?: string;
+    path?: string;
+    component?: string;
+    isFrame?: string;
+    isCache?: string;
+    menuType?: string;
+    visible?: string;
+    status?: string;
+    perms?: string;
+    icon?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  };
+
+  export type DictTypeType = {
+    dictId: number;
+    dictName: string;
+    dictType: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  };
+
+  export type DictTypeListParams = {
+    dictId?: string;
+    dictName?: string;
+    dictType?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  };
+}
diff --git a/react-ui/src/types/monitor/cache.d.ts b/react-ui/src/types/monitor/cache.d.ts
new file mode 100644
index 0000000..bfbab1a
--- /dev/null
+++ b/react-ui/src/types/monitor/cache.d.ts
@@ -0,0 +1,153 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+declare namespace API.Monitor {
+
+  export type CommandInfo = {
+    name: string;
+    value: string;
+  };
+
+  export type CacheRuntimeInfo = {
+    active_defrag_hits: string;
+    active_defrag_key_hits: string;
+    active_defrag_key_misses: string;
+    active_defrag_misses: string;
+    active_defrag_running: string;
+    allocator_active: string;
+    allocator_allocated: string;
+    allocator_frag_bytes: string;
+    allocator_frag_ratio: string;
+    allocator_resident: string;
+    allocator_rss_bytes: string;
+    allocator_rss_ratio: string;
+    aof_current_rewrite_time_sec: string;
+    aof_enabled: string;
+    aof_last_bgrewrite_status: string;
+    aof_last_cow_size: string;
+    aof_last_rewrite_time_sec: string;
+    aof_last_write_status: string;
+    aof_rewrite_in_progress: string;
+    aof_rewrite_scheduled: string;
+    arch_bits: string;
+    atomicvar_api: string;
+    blocked_clients: string;
+    client_recent_max_input_buffer: string;
+    client_recent_max_output_buffer: string;
+    cluster_enabled: string;
+    config_file: string;
+    configured_hz: string;
+    connected_clients: string;
+    connected_slaves: string;
+    db0: string;
+    db1: string;
+    evicted_keys: string;
+    executable: string;
+    expired_keys: string;
+    expired_stale_perc: string;
+    expired_time_cap_reached_count: string;
+    gcc_version: string;
+    hz: string;
+    instantaneous_input_kbps: number;
+    instantaneous_ops_per_sec: string;
+    instantaneous_output_kbps: number;
+    keyspace_hits: string;
+    keyspace_misses: string;
+    latest_fork_usec: string;
+    lazyfree_pending_objects: string;
+    loading: string;
+    lru_clock: string;
+    master_repl_offset: string;
+    master_replid: string;
+    master_replid2: string;
+    maxmemory: string;
+    maxmemory_human: string;
+    maxmemory_policy: string;
+    mem_allocator: string;
+    mem_aof_buffer: string;
+    mem_clients_normal: string;
+    mem_clients_slaves: string;
+    mem_fragmentation_bytes: string;
+    mem_fragmentation_ratio: string;
+    mem_not_counted_for_evict: string;
+    mem_replication_backlog: string;
+    migrate_cached_sockets: string;
+    multiplexing_api: string;
+    number_of_cached_scripts: string;
+    os: string;
+    process_id: string;
+    pubsub_channels: string;
+    pubsub_patterns: string;
+    rdb_bgsave_in_progress: string;
+    rdb_changes_since_last_save: string;
+    rdb_current_bgsave_time_sec: string;
+    rdb_last_bgsave_status: string;
+    rdb_last_bgsave_time_sec: string;
+    rdb_last_cow_size: string;
+    rdb_last_save_time: string;
+    redis_build_id: string;
+    redis_git_dirty: string;
+    redis_git_sha1: string;
+    redis_mode: string;
+    redis_version: string;
+    rejected_connections: string;
+    repl_backlog_active: string;
+    repl_backlog_first_byte_offset: string;
+    repl_backlog_histlen: string;
+    repl_backlog_size: string;
+    role: string;
+    rss_overhead_bytes: string;
+    rss_overhead_ratio: string;
+    run_id: string;
+    second_repl_offset: string;
+    slave_expires_tracked_keys: string;
+    sync_full: string;
+    sync_partial_err: string;
+    sync_partial_ok: string;
+    tcp_port: string;
+    total_commands_processed: string;
+    total_connections_received: string;
+    total_net_input_bytes: string;
+    total_net_output_bytes: string;
+    total_system_memory: number;
+    total_system_memory_human: string;
+    uptime_in_days: string;
+    uptime_in_seconds: string;
+    used_cpu_sys: string;
+    used_cpu_sys_children: string;
+    used_cpu_user: string;
+    used_cpu_user_children: number;
+    used_memory: number;
+    used_memory_dataset: number;
+    used_memory_dataset_perc: string;
+    used_memory_human: string;
+    used_memory_lua: string;
+    used_memory_lua_human: string;
+    used_memory_overhead: string;
+    used_memory_peak: string;
+    used_memory_peak_human: string;
+    used_memory_peak_perc: string;
+    used_memory_rss: string;
+    used_memory_rss_human: string;
+    used_memory_scripts: string;
+    used_memory_scripts_human: string;
+    used_memory_startup: string;
+  };
+
+  export type CacheInfo = {
+    commandStats: CommandInfo[];
+    dbSize: number;
+    info: CacheRuntimeInfo;
+  };
+
+  export type CacheInfoResult = {
+    data: CacheInfo;
+    code: number;
+    msg: string;
+  };
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/cacheList.d.ts b/react-ui/src/types/monitor/cacheList.d.ts
new file mode 100644
index 0000000..e2de8cd
--- /dev/null
+++ b/react-ui/src/types/monitor/cacheList.d.ts
@@ -0,0 +1,36 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2022/06/27
+ * 
+ * */
+
+declare namespace API.Monitor {
+
+  export type CacheContent = {
+    cacheKey: string;
+    cacheName: string;
+    cacheValue: string;
+    remark: string;
+  };
+
+  export type CacheNamesResponse = {
+    data: CacheContent[];
+    code: number;
+    msg: string;
+  };
+
+  export type CacheKeysResponse = {
+    data: string[];
+    code: number;
+    msg: string;
+  };
+
+  export type CacheValueResponse = {
+    data: CacheContent;
+    code: number;
+    msg: string;
+  };
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/job.d.ts b/react-ui/src/types/monitor/job.d.ts
new file mode 100644
index 0000000..5b1c965
--- /dev/null
+++ b/react-ui/src/types/monitor/job.d.ts
@@ -0,0 +1,58 @@
+/**
+ * 定时任务调度 Model Declare
+ * 
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+declare namespace API.Monitor {
+
+  export interface Job {
+    jobId: number;
+    jobName: string;
+    jobGroup: string;
+    invokeTarget: string;
+    cronExpression: string;
+    misfirePolicy: string;
+    concurrent: string;
+    nextValidTime: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface JobListParams {
+    jobId?: string;
+    jobName?: string;
+    jobGroup?: string;
+    invokeTarget?: string;
+    cronExpression?: string;
+    misfirePolicy?: string;
+    concurrent?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface JobInfoResult { 
+    code: number;
+    msg: string;
+    data: Job;
+  } 
+
+   export interface JobPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Job>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/jobLog.d.ts b/react-ui/src/types/monitor/jobLog.d.ts
new file mode 100644
index 0000000..e62cb9f
--- /dev/null
+++ b/react-ui/src/types/monitor/jobLog.d.ts
@@ -0,0 +1,47 @@
+/**
+ * 定时任务调度日志 Model Declare
+ * 
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+declare namespace API.Monitor {
+
+  export interface JobLog {
+    jobLogId: number;
+    jobName: string;
+    jobGroup: string;
+    invokeTarget: string;
+    jobMessage: string;
+    status: string;
+    exceptionInfo: string;
+    createTime: Date;
+  }
+
+  export interface JobLogListParams {
+    jobLogId?: string;
+    jobName?: string;
+    jobGroup?: string;
+    invokeTarget?: string;
+    jobMessage?: string;
+    status?: string;
+    exceptionInfo?: string;
+    createTime?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface JobLogInfoResult { 
+    code: number;
+    msg: string;
+    data: JobLog;
+  } 
+
+   export interface JobLogPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<JobLog>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/logininfor.d.ts b/react-ui/src/types/monitor/logininfor.d.ts
new file mode 100644
index 0000000..ef9208a
--- /dev/null
+++ b/react-ui/src/types/monitor/logininfor.d.ts
@@ -0,0 +1,43 @@
+
+declare namespace API.Monitor {
+
+  export interface Logininfor {
+    infoId: number;
+    userName: string;
+    ipaddr: string;
+    loginLocation: string;
+    browser: string;
+    os: string;
+    status: string;
+    msg: string;
+    loginTime: Date;
+  }
+
+  export interface LogininforListParams {
+    infoId?: string;
+    userName?: string;
+    ipaddr?: string;
+    loginLocation?: string;
+    browser?: string;
+    os?: string;
+    status?: string;
+    msg?: string;
+    loginTime?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface LogininforInfoResult { 
+    code: number;
+    msg: string;
+    data: Logininfor;
+  } 
+
+   export interface LogininforPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Logininfor>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/online.d.ts b/react-ui/src/types/monitor/online.d.ts
new file mode 100644
index 0000000..5b8e0dd
--- /dev/null
+++ b/react-ui/src/types/monitor/online.d.ts
@@ -0,0 +1,55 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+declare namespace API.Monitor {
+
+  export type OnlineUserType = {
+    tokenId: string;
+    userName: string;
+    ipaddr: string;
+    loginLocation: string;
+    browser: string;
+    os: string;
+    deptName: string;
+    loginTime: string;
+  };
+
+  export type OnlineUserListPagination = {
+    total: number;
+    pageSize: number;
+    current: number;
+  };
+
+  export type OnlineUserListData = {
+    list: OnlineUserType[];
+    pagination: Partial<OnlineUserListPagination>;
+  };
+
+  export type OnlineUserListParams = {
+    tokenId?: string;
+    userName?: string;
+    ipaddr?: string;
+    loginLocation?: string;
+    browser?: string;
+    os?: string;
+    deptName?: string;
+    loginTime?: string;
+    pageSize?: string;
+    current?: string;
+    pageNum?: string;
+    filter?: string;
+    sorter?: string;
+  };  
+
+  export interface OnlineUserPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<OnlineUser>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/operlog.d.ts b/react-ui/src/types/monitor/operlog.d.ts
new file mode 100644
index 0000000..96ee27e
--- /dev/null
+++ b/react-ui/src/types/monitor/operlog.d.ts
@@ -0,0 +1,57 @@
+
+declare namespace API.Monitor {
+
+  export interface Operlog {
+    operId: number;
+    title: string;
+    businessType: number;
+    method: string;
+    requestMethod: string;
+    operatorType: number;
+    operName: string;
+    deptName: string;
+    operUrl: string;
+    operIp: string;
+    operLocation: string;
+    operParam: string;
+    jsonResult: string;
+    status: number;
+    errorMsg: string;
+    operTime: Date;
+  }
+
+  export interface OperlogListParams {
+    operId?: string;
+    title?: string;
+    businessType?: string;
+    method?: string;
+    requestMethod?: string;
+    operatorType?: string;
+    operName?: string;
+    deptName?: string;
+    operUrl?: string;
+    operIp?: string;
+    operLocation?: string;
+    operParam?: string;
+    jsonResult?: string;
+    status?: string;
+    errorMsg?: string;
+    operTime?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface OperlogInfoResult { 
+    code: number;
+    msg: string;
+    data: Operlog;
+  } 
+
+   export interface OperlogPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Operlog>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/server.d.ts b/react-ui/src/types/monitor/server.d.ts
new file mode 100644
index 0000000..8a32d16
--- /dev/null
+++ b/react-ui/src/types/monitor/server.d.ts
@@ -0,0 +1,84 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+
+declare namespace API.Monitor {
+
+  export type CpuInfoType = {
+    cpuNum: string;
+    total: string;
+    sys: string;
+    used: string;
+    wait: string;
+    free: string;
+  };
+
+  export type MemInfoType = {
+    total: string;
+    used: string;
+    usage: string;
+    free: string;
+  };
+
+  export type SysInfoType = {
+    computerIp: string;
+    computerName: string;
+    osArch: string;
+    osName: string;
+    userDir: string;
+  };
+
+  export type JvmInfoType = {
+    free: string;
+    home: string;
+    max: string;
+    name: string;
+    runTime: string;
+    startTime: string;
+    total: string;
+    usage: string;
+    used: string;
+    version: string;
+  };
+
+  export type DiskInfoType = {
+    dirName: string;
+    free: string;
+    sysTypeName: string;
+    total: string;
+    typeName: string;
+    usage: string;
+    used: string;
+  };
+
+  export type ServerInfoType = {
+    cpu: CpuInfoType;
+    mem: MemInfoType;
+    jvm: JvmInfoType;
+    sys: SysInfoType;
+    sysFiles: DiskInfoType[];
+  };
+
+  export type ServerInfoResponseType = {
+    data: ServerInfoType;
+    code: number;
+    msg: string;
+  };
+
+  export type CpuRowType = {
+    name: string;
+    value: string;
+  };
+
+  export type MemRowType = {
+    name: string;
+    mem: string;
+    jvm: string;
+  };
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/config.d.ts b/react-ui/src/types/system/config.d.ts
new file mode 100644
index 0000000..7e7e43e
--- /dev/null
+++ b/react-ui/src/types/system/config.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+  export interface Config {
+    configId: number;
+    configName: string;
+    configKey: string;
+    configValue: string;
+    configType: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface ConfigListParams {
+    configId?: string;
+    configName?: string;
+    configKey?: string;
+    configValue?: string;
+    configType?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface ConfigInfoResult { 
+    code: number;
+    msg: string;
+    data: Config;
+  } 
+
+   export interface ConfigPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Config>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/dept.d.ts b/react-ui/src/types/system/dept.d.ts
new file mode 100644
index 0000000..14ad274
--- /dev/null
+++ b/react-ui/src/types/system/dept.d.ts
@@ -0,0 +1,53 @@
+
+declare namespace API.System {
+
+  interface Dept {
+    deptId: number;
+    parentId: number;
+    ancestors: string;
+    deptName: string;
+    orderNum: number;
+    leader: string;
+    phone: string;
+    email: string;
+    status: string;
+    delFlag: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+  }
+
+  export interface DeptListParams {
+    deptId?: string;
+    parentId?: string;
+    ancestors?: string;
+    deptName?: string;
+    orderNum?: string;
+    leader?: string;
+    phone?: string;
+    email?: string;
+    status?: string;
+    delFlag?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface DeptInfoResult { 
+    code: number;
+    msg: string;
+    data: Dept;
+  } 
+
+   export interface DeptPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    data: Array<Dept>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/dict-data.d.ts b/react-ui/src/types/system/dict-data.d.ts
new file mode 100644
index 0000000..30373f5
--- /dev/null
+++ b/react-ui/src/types/system/dict-data.d.ts
@@ -0,0 +1,53 @@
+declare namespace API.System {
+  interface DictData {
+    dictCode: number;
+    dictSort: number;
+    dictLabel: string;
+    dictValue: string;
+    dictType: string;
+    cssClass: string;
+    listClass: string;
+    isDefault: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface DictDataListParams {
+    dictCode?: string;
+    dictSort?: string;
+    dictLabel?: string;
+    dictValue?: string;
+    dictType?: string;
+    cssClass?: string;
+    listClass?: string;
+    isDefault?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  }
+
+  export interface DictDataInfoResult {
+    current: number;
+    pageSize: number;
+    total: number;
+    data: DictData;
+  }
+
+  export interface DictDataPageResult {
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<DictData>;
+  }
+}
diff --git a/react-ui/src/types/system/dict.d.ts b/react-ui/src/types/system/dict.d.ts
new file mode 100644
index 0000000..eef354a
--- /dev/null
+++ b/react-ui/src/types/system/dict.d.ts
@@ -0,0 +1,48 @@
+declare namespace API.System {
+  export interface DictType {
+    dictId: number;
+    dictName: string;
+    dictType: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface DictTypeListParams {
+    dictId?: string;
+    dictName?: string;
+    dictType?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  }
+
+  export interface DictTypeInfoResult {
+    code: number;
+    msg: string;
+    data: Dict;
+  }
+
+  export interface DictTypePageResult {
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Dict>;
+  }
+
+  export interface DictTypeResult {
+    code: number;
+    msg: string;
+    data: Array<Dict>;
+  }
+}
diff --git a/react-ui/src/types/system/menu.d.ts b/react-ui/src/types/system/menu.d.ts
new file mode 100644
index 0000000..15e68ce
--- /dev/null
+++ b/react-ui/src/types/system/menu.d.ts
@@ -0,0 +1,66 @@
+
+declare namespace API.System {
+
+  interface Menu {
+    menuId: number;
+    menuName: string;
+    parentId: number;
+    orderNum: number;
+    path: string;
+    component: string;
+    query: string;
+    isFrame: number;
+    isCache: number;
+    menuType: string;
+    visible: string;
+    status: string;
+    perms: string;
+    icon: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface MenuListParams {
+    menuId?: string;
+    menuName?: string;
+    parentId?: string;
+    orderNum?: string;
+    path?: string;
+    component?: string;
+    query?: string;
+    isFrame?: string;
+    isCache?: string;
+    menuType?: string;
+    visible?: string;
+    status?: string;
+    perms?: string;
+    icon?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  }
+
+  export interface MenuInfoResult { 
+    current: number;
+    pageSize: number;
+    total: number;
+    data: Menu;
+  } 
+
+   export interface MenuPageResult { 
+    current: number;
+    pageSize: number;
+    total: number;
+    data: Array<Menu>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/notice.d.ts b/react-ui/src/types/system/notice.d.ts
new file mode 100644
index 0000000..89a77ee
--- /dev/null
+++ b/react-ui/src/types/system/notice.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+  export interface Notice {
+    noticeId: number;
+    noticeTitle: string;
+    noticeType: string;
+    noticeContent: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface NoticeListParams {
+    noticeId?: string;
+    noticeTitle?: string;
+    noticeType?: string;
+    noticeContent?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface NoticeInfoResult { 
+    code: number;
+    msg: string;
+    data: Notice;
+  } 
+
+   export interface NoticePageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Notice>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/post.d.ts b/react-ui/src/types/system/post.d.ts
new file mode 100644
index 0000000..772140c
--- /dev/null
+++ b/react-ui/src/types/system/post.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+  interface Post {
+    postId: number;
+    postCode: string;
+    postName: string;
+    postSort: number;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface PostListParams {
+    postId?: string;
+    postCode?: string;
+    postName?: string;
+    postSort?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface PostInfoResult { 
+    code: number;
+    msg: string;
+    data: Post;
+  } 
+
+   export interface PostPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Post>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/role.d.ts b/react-ui/src/types/system/role.d.ts
new file mode 100644
index 0000000..53191c9
--- /dev/null
+++ b/react-ui/src/types/system/role.d.ts
@@ -0,0 +1,65 @@
+
+declare namespace API.System {
+
+  interface Role {
+    roleId: number;
+    roleName: string;
+    roleKey: string;
+    roleSort: number;
+    dataScope: string;
+    menuCheckStrictly: number;
+    deptCheckStrictly: number;
+    status: string;
+    delFlag: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface RoleListParams {
+    roleId?: string;
+    roleName?: string;
+    roleKey?: string;
+    roleSort?: string;
+    dataScope?: string;
+    menuCheckStrictly?: string;
+    deptCheckStrictly?: string;
+    status?: string;
+    delFlag?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface RoleInfoResult { 
+    code: number;
+    msg: string;
+    data: Role;
+  } 
+
+   export interface RolePageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Role>;
+  }
+
+  export type RoleMenuNode = {
+    id: number|string;
+    label: string;
+    children?: Array<RoleMenuNode>;
+  }
+  export interface RoleMenuResult { 
+    code: number;
+    msg: string;
+    checkedKeys: number[];
+    menus: Array<RoleMenuNode>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/user.d.ts b/react-ui/src/types/system/user.d.ts
new file mode 100644
index 0000000..5751956
--- /dev/null
+++ b/react-ui/src/types/system/user.d.ts
@@ -0,0 +1,71 @@
+
+declare namespace API.System {
+
+  interface User {
+    userId: number;
+    deptId: number;
+    userName: string;
+    nickName: string;
+    userType: string;
+    email: string;
+    phonenumber: string;
+    sex: string;
+    avatar: string;
+    password: string;
+    status: string;
+    delFlag: string;
+    loginIp: string;
+    loginDate: Date;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface UserListParams {
+    userId?: string;
+    deptId?: string;
+    userName?: string;
+    nickName?: string;
+    userType?: string;
+    email?: string;
+    phonenumber?: string;
+    sex?: string;
+    avatar?: string;
+    password?: string;
+    status?: string;
+    delFlag?: string;
+    loginIp?: string;
+    loginDate?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  }
+
+  export interface UserInfoResult { 
+    current: number;
+    pageSize: number;
+    total: number;
+    data: User;
+    posts: [];
+    postIds: any;
+    roleId: number;
+    roleIds: [];
+    roles: [];
+  } 
+
+   export interface UserPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<User>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/typings.d.ts b/react-ui/src/types/typings.d.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/react-ui/src/types/typings.d.ts
diff --git a/react-ui/src/typings.d.ts b/react-ui/src/typings.d.ts
new file mode 100644
index 0000000..742f70c
--- /dev/null
+++ b/react-ui/src/typings.d.ts
@@ -0,0 +1,20 @@
+declare module 'slash2';
+declare module '*.css';
+declare module '*.less';
+declare module '*.scss';
+declare module '*.sass';
+declare module '*.svg';
+declare module '*.png';
+declare module '*.jpg';
+declare module '*.jpeg';
+declare module '*.gif';
+declare module '*.bmp';
+declare module '*.tiff';
+declare module 'omit.js';
+declare module 'numeral';
+declare module '@antv/data-set';
+declare module 'mockjs';
+declare module 'react-fittext';
+declare module 'bizcharts-plugin-slider';
+
+declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
diff --git a/react-ui/src/utils/IconUtil.ts b/react-ui/src/utils/IconUtil.ts
new file mode 100644
index 0000000..31102ee
--- /dev/null
+++ b/react-ui/src/utils/IconUtil.ts
@@ -0,0 +1,20 @@
+import * as AntdIcons from '@ant-design/icons';
+import React from 'react';
+
+const allIcons: Record<string, any> = AntdIcons;
+
+export function getIcon(name: string): React.ReactNode | string {
+  const icon = allIcons[name];
+  return icon || '';
+}
+
+export function createIcon(icon: string | any): React.ReactNode | string {
+  if (typeof icon === 'object') {
+    return icon;
+  }
+  const ele = allIcons[icon];
+  if (ele) {
+    return React.createElement(allIcons[icon]);
+  }
+  return '';
+}
diff --git a/react-ui/src/utils/downloadfile.ts b/react-ui/src/utils/downloadfile.ts
new file mode 100644
index 0000000..5d56cda
--- /dev/null
+++ b/react-ui/src/utils/downloadfile.ts
@@ -0,0 +1,63 @@
+import { request } from '@umijs/max';
+
+const mimeMap = {
+  xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+  zip: 'application/zip',
+};
+
+/**
+ * 解析blob响应内容并下载
+ * @param {*} res blob响应内容
+ * @param {String} mimeType MIME类型
+ */
+export function resolveBlob(res: any, mimeType: string) {
+  const aLink = document.createElement('a');
+  const blob = new Blob([res.data], { type: mimeType });
+  // //从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
+  const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*');
+  // console.log(res);
+  const contentDisposition = decodeURI(res.headers['content-disposition']);
+  const result = patt.exec(contentDisposition);
+  let fileName = result ? result[1] : 'file';
+  fileName = fileName.replace(/"/g, '');
+  aLink.style.display = 'none';
+  aLink.href = URL.createObjectURL(blob);
+  aLink.setAttribute('download', fileName); // 设置下载文件名称
+  document.body.appendChild(aLink);
+  aLink.click();
+  URL.revokeObjectURL(aLink.href); // 清除引用
+  document.body.removeChild(aLink);
+}
+
+export function downLoadZip(url: string) {
+  request(url, {
+    method: 'GET',
+    responseType: 'blob',
+    getResponse: true,
+  }).then((res) => {
+    resolveBlob(res, mimeMap.zip);
+  });
+}
+
+export async function downLoadXlsx(url: string, params: any, fileName: string) {
+  return request(url, {
+    ...params,
+    method: 'POST',
+    responseType: 'blob',
+  }).then((data) => {
+    const aLink = document.createElement('a');
+    const blob = data as any; // new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+    aLink.style.display = 'none';
+    aLink.href = URL.createObjectURL(blob);
+    aLink.setAttribute('download', fileName); // 设置下载文件名称
+    document.body.appendChild(aLink);
+    aLink.click();
+    URL.revokeObjectURL(aLink.href); // 清除引用
+    document.body.removeChild(aLink);
+  });
+}
+
+
+export function download(fileName: string) {
+  window.location.href = `/api/common/download?fileName=${encodeURI(fileName)}&delete=${true}`;
+}
diff --git a/react-ui/src/utils/options.ts b/react-ui/src/utils/options.ts
new file mode 100644
index 0000000..79ee803
--- /dev/null
+++ b/react-ui/src/utils/options.ts
@@ -0,0 +1,12 @@
+import { DictValueEnumObj } from "@/components/DictTag";
+import { ProSchemaValueEnumObj, ProSchemaValueEnumType } from "@ant-design/pro-components";
+
+export function getValueEnumLabel(options: DictValueEnumObj | ProSchemaValueEnumObj, val: string | number | undefined, defaultValue?: string) {
+    if (val !== undefined) {
+       const data = options[val] as ProSchemaValueEnumType;
+       if(data) {
+        return data.text;
+       }
+    }
+    return defaultValue?defaultValue:val;
+}
diff --git a/react-ui/src/utils/permission.ts b/react-ui/src/utils/permission.ts
new file mode 100644
index 0000000..e7c8a28
--- /dev/null
+++ b/react-ui/src/utils/permission.ts
@@ -0,0 +1,64 @@
+// /**
+//  * 字符权限校验
+//  * @param {Array} value 校验值
+//  * @returns {Boolean}
+//  */
+export function matchPerms(permissions: string[], value: string[]) {
+  if (value && value instanceof Array && value.length > 0) {
+    const permissionDatas = value;
+    const all_permission = '*:*:*';
+    const hasPermission = permissions.some((permission) => {
+      return all_permission === permission || permissionDatas.includes(permission);
+    });
+    if (!hasPermission) {
+      return false;
+    }
+    return true;
+  }
+  console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
+  return false;
+}
+
+export function matchPerm(permissions: string[], value: string) {
+  if (value && value.length > 0) {
+    const permissionDatas = value;
+    const all_permission = '*:*:*';
+    const hasPermission = permissions.some((permission) => {
+      return all_permission === permission || permissionDatas === permission;
+    });
+    if (!hasPermission) {
+      return false;
+    }
+    return true;
+  }
+  console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
+  return false;
+}
+
+export function matchPermission(permissions: string[] | undefined, value: any): boolean {
+  if (permissions === undefined) return false;
+  const type = typeof value;
+  if (type === 'string') {
+    return matchPerm(permissions, value);
+  }
+  return matchPerms(permissions, value);
+}
+
+/**
+ * 角色权限校验
+ * @param {Array} value 校验值
+ * @returns {Boolean}
+ */
+export function checkRole(roles: API.System.Role[] | undefined, value: string[]) {
+  if (roles && value && value.length > 0) {
+    for (let i = 0; i < roles?.length; i++) {
+      for (let j = 0; j < value?.length; j++) {
+        if (value[j] === roles[i].roleKey) {
+          return true;
+        }
+      }
+    }
+  }
+  console.error(`need roles! Like checkRole="['admin','editor']"`);
+  return false;
+}
diff --git a/react-ui/src/utils/tree.ts b/react-ui/src/utils/tree.ts
new file mode 100644
index 0000000..d75395e
--- /dev/null
+++ b/react-ui/src/utils/tree.ts
@@ -0,0 +1,93 @@
+import { DataNode } from 'antd/es/tree';
+import { parse } from 'querystring';
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ */
+export function buildTreeData(
+  data: any[],
+  id: string,
+  name: string,
+  parentId: string,
+  parentName: string,
+  children: string,
+) {
+  const config = {
+    id: id || 'id',
+    name: name || 'name',
+    parentId: parentId || 'parentId',
+    parentName: parentName || 'parentName',
+    childrenList: children || 'children',
+  };
+
+  const childrenListMap: any[] = [];
+  const nodeIds: any[] = [];
+  const tree: any[] = [];
+  data.forEach((item) => {
+    const d = item;
+    const pId = d[config.parentId];
+    if (!childrenListMap[pId]) {
+      childrenListMap[pId] = [];
+    }
+    d.key = d[config.id];
+    d.title = d[config.name];
+    d.value = d[config.id];
+    d[config.childrenList] = null;
+    nodeIds[d[config.id]] = d;
+    childrenListMap[pId].push(d);
+  });
+
+  data.forEach((item: any) => {
+    const d = item;
+    const pId = d[config.parentId];
+    if (!nodeIds[pId]) {
+      d[config.parentName] = '';
+      tree.push(d);
+    }
+  });
+
+  function adaptToChildrenList(item: any) {
+    const o = item;
+    if (childrenListMap[o[config.id]]) {
+      if (!o[config.childrenList]) {
+        o[config.childrenList] = [];
+      }
+      o[config.childrenList] = childrenListMap[o[config.id]];
+    }
+    if (o[config.childrenList]) {
+      o[config.childrenList].forEach((child: any) => {
+        const c = child;
+        c[config.parentName] = o[config.name];
+        adaptToChildrenList(c);
+      });
+    }
+  }
+
+  tree.forEach((t: any) => {
+    adaptToChildrenList(t);
+  });
+
+  return tree;
+}
+
+export const getPageQuery = () => parse(window.location.href.split('?')[1]);
+
+export function formatTreeData(arrayList: any): DataNode[] {
+  const treeSelectData: DataNode[] = arrayList.map((item: any) => {
+    const node: DataNode = {
+      id: item.id,
+      title: item.label,
+      key: `${item.id}`,
+      value: item.id,
+    } as DataNode;
+    if (item.children) {
+      node.children = formatTreeData(item.children);
+    }
+    return node;
+  });
+  return treeSelectData;
+}
diff --git a/react-ui/tests/setupTests.jsx b/react-ui/tests/setupTests.jsx
new file mode 100644
index 0000000..952561d
--- /dev/null
+++ b/react-ui/tests/setupTests.jsx
@@ -0,0 +1,64 @@
+const localStorageMock = {
+  getItem: jest.fn(),
+  setItem: jest.fn(),
+  removeItem: jest.fn(),
+  clear: jest.fn(),
+};
+
+global.localStorage = localStorageMock;
+
+Object.defineProperty(URL, 'createObjectURL', {
+  writable: true,
+  value: jest.fn(),
+});
+
+class Worker {
+  constructor(stringUrl) {
+    this.url = stringUrl;
+    this.onmessage = () => {};
+  }
+
+  postMessage(msg) {
+    this.onmessage(msg);
+  }
+}
+window.Worker = Worker;
+
+/* eslint-disable global-require */
+if (typeof window !== 'undefined') {
+  // ref: https://github.com/ant-design/ant-design/issues/18774
+  if (!window.matchMedia) {
+    Object.defineProperty(global.window, 'matchMedia', {
+      writable: true,
+      configurable: true,
+      value: jest.fn(() => ({
+        matches: false,
+        addListener: jest.fn(),
+        removeListener: jest.fn(),
+      })),
+    });
+  }
+  if (!window.matchMedia) {
+    Object.defineProperty(global.window, 'matchMedia', {
+      writable: true,
+      configurable: true,
+      value: jest.fn((query) => ({
+        matches: query.includes('max-width'),
+        addListener: jest.fn(),
+        removeListener: jest.fn(),
+      })),
+    });
+  }
+}
+const errorLog = console.error;
+Object.defineProperty(global.window.console, 'error', {
+  writable: true,
+  configurable: true,
+  value: (...rest) => {
+    const logStr = rest.join('');
+    if (logStr.includes('Warning: An update to %s inside a test was not wrapped in act(...)')) {
+      return;
+    }
+    errorLog(...rest);
+  },
+});
diff --git a/react-ui/tsconfig.json b/react-ui/tsconfig.json
new file mode 100644
index 0000000..85733f6
--- /dev/null
+++ b/react-ui/tsconfig.json
@@ -0,0 +1,23 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "moduleResolution": "node",
+    "importHelpers": true,
+    "jsx": "preserve",
+    "esModuleInterop": true,
+    "sourceMap": true,
+    "baseUrl": "./",
+    "skipLibCheck": true,
+    "experimentalDecorators": true,
+    "strict": true,
+    "resolveJsonModule": true,
+    "allowSyntheticDefaultImports": true,
+    "paths": {
+      "@/*": ["./src/*"],
+      "@@/*": ["./src/.umi/*"],
+      "@@test/*": ["./src/.umi-test/*"]
+    }
+  },
+  "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"]
+}
diff --git a/react-ui/types/cache/cache.json b/react-ui/types/cache/cache.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/react-ui/types/cache/cache.json
@@ -0,0 +1 @@
+{}
diff --git a/react-ui/types/cache/login.cache.json b/react-ui/types/cache/login.cache.json
new file mode 100644
index 0000000..81109b4
--- /dev/null
+++ b/react-ui/types/cache/login.cache.json
@@ -0,0 +1,386 @@
+{
+  "GET /api/currentUser": {
+    "res": {
+      "data": {
+        "name": "Serati Ma",
+        "avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
+        "userid": "00000001",
+        "email": "antdesign@alipay.com",
+        "signature": "海纳百川,有容乃大",
+        "title": "交互专家",
+        "group": "蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED",
+        "tags": [
+          {
+            "key": "0",
+            "label": "很有想法的"
+          },
+          {
+            "key": "1",
+            "label": "专注设计"
+          },
+          {
+            "key": "2",
+            "label": "辣~"
+          },
+          {
+            "key": "3",
+            "label": "大长腿"
+          },
+          {
+            "key": "4",
+            "label": "川妹子"
+          },
+          {
+            "key": "5",
+            "label": "海纳百川"
+          }
+        ],
+        "notifyCount": 12,
+        "unreadCount": 11,
+        "country": "China",
+        "geographic": {
+          "province": {
+            "label": "浙江省",
+            "key": "330000"
+          },
+          "city": {
+            "label": "杭州市",
+            "key": "330100"
+          }
+        },
+        "address": "西湖区工专路 77 号",
+        "phone": "0752-268888888"
+      }
+    },
+    "query": {
+      "token ": " 123"
+    },
+    "payload": {},
+    "types": "/** GET /api/currentUser */\nexport type GET_API_CURRENT_USER_QUERY = {\n  /** example:  123 */\n        token : string\n}\n  \n\nexport type GET_API_CURRENT_USER_PAYLOAD = {\n  \n}\n  \n\nexport type GET_API_CURRENT_USER_RES = {\n  /** example: {\"name\": \"Serati Ma\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png\", \"userid\": \"00000001\", \"email\": \"antdesign@alipay.com\", \"signature\": \"海纳百川,有容乃大\", \"title\": \"交互专家\", \"group\": \"蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED\", \"tags\": [{\"key\": \"0\", \"label\": \"很有想法的\"}, {\"key\": \"1\", \"label\": \"专注设计\"}, {\"key\": \"2\", \"label\": \"辣~\"}, {\"key\": \"3\", \"label\": \"大长腿\"}, {\"key\": \"4\", \"label\": \"川妹子\"}, {\"key\": \"5\", \"label\": \"海纳百川\"}], \"notifyCount\": 12, \"unreadCount\": 11, \"country\": \"China\", \"geographic\": {\"province\": {\"label\": \"浙江省\", \"key\": \"330000\"}, \"city\": {\"label\": \"杭州市\", \"key\": \"330100\"}}, \"address\": \"西湖区工专路 77 号\", \"phone\": \"0752-268888888\"} */\n        data: {\n      name: string,\navatar: string,\nuserid: string,\nemail: string,\nsignature: string,\ntitle: string,\ngroup: string,\ntags: {\n      key: string,\nlabel: string\n      }[],\nnotifyCount: number,\nunreadCount: number,\ncountry: string,\ngeographic: {\n      province: {\n      label: string,\nkey: string\n      },\ncity: {\n      label: string,\nkey: string\n      }\n      },\naddress: string,\nphone: string\n      }\n}\n  "
+  },
+  "GET /api/rule": {
+    "res": {
+      "data": [
+        {
+          "key": 99,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 99",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 503,
+          "status": "0",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 81
+        },
+        {
+          "key": 98,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 98",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 164,
+          "status": "0",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 12
+        },
+        {
+          "key": 97,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 97",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 174,
+          "status": "1",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 81
+        },
+        {
+          "key": 96,
+          "disabled": true,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 96",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 914,
+          "status": "0",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 7
+        },
+        {
+          "key": 95,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 95",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 698,
+          "status": "2",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 82
+        },
+        {
+          "key": 94,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 94",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 488,
+          "status": "1",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 14
+        },
+        {
+          "key": 93,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 93",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 580,
+          "status": "2",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 77
+        },
+        {
+          "key": 92,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 92",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 244,
+          "status": "3",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 58
+        },
+        {
+          "key": 91,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 91",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 959,
+          "status": "0",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 66
+        },
+        {
+          "key": 90,
+          "disabled": true,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 90",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 958,
+          "status": "0",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 72
+        },
+        {
+          "key": 89,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 89",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 301,
+          "status": "2",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 2
+        },
+        {
+          "key": 88,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 88",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 277,
+          "status": "1",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 12
+        },
+        {
+          "key": 87,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 87",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 810,
+          "status": "1",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 82
+        },
+        {
+          "key": 86,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 86",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 780,
+          "status": "3",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 22
+        },
+        {
+          "key": 85,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 85",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 705,
+          "status": "3",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 12
+        },
+        {
+          "key": 84,
+          "disabled": true,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 84",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 203,
+          "status": "0",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 79
+        },
+        {
+          "key": 83,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 83",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 491,
+          "status": "2",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 59
+        },
+        {
+          "key": 82,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 82",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 73,
+          "status": "0",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 100
+        },
+        {
+          "key": 81,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+          "name": "TradeCode 81",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 406,
+          "status": "3",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 61
+        },
+        {
+          "key": 80,
+          "disabled": false,
+          "href": "https://ant.design",
+          "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+          "name": "TradeCode 80",
+          "owner": "曲丽丽",
+          "desc": "这是一段描述",
+          "callNo": 112,
+          "status": "2",
+          "updatedAt": "2022-12-06T05:00:57.040Z",
+          "createdAt": "2022-12-06T05:00:57.040Z",
+          "progress": 20
+        }
+      ],
+      "total": 100,
+      "success": true,
+      "pageSize": 20,
+      "current": 1
+    },
+    "query": {
+      "token ": " 123",
+      "current": "1",
+      "pageSize": "20"
+    },
+    "payload": {},
+    "types": "/** GET /api/rule */\nexport type GET_API_RULE_QUERY = {\n  /** example:  123 */\n        token : string;\n  /** example: 1 */\n        current: string;\n  /** example: 20 */\n        pageSize: string\n}\n  \n\nexport type GET_API_RULE_PAYLOAD = {\n  \n}\n  \n\nexport type GET_API_RULE_RES = {\n  /** example: [{\"key\": 99, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 99\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 503, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 81}, {\"key\": 98, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 98\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 164, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 12}, {\"key\": 97, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 97\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 174, \"status\": \"1\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 81}, {\"key\": 96, \"disabled\": true, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 96\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 914, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 7}, {\"key\": 95, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 95\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 698, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 82}, {\"key\": 94, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 94\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 488, \"status\": \"1\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 14}, {\"key\": 93, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 93\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 580, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 77}, {\"key\": 92, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 92\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 244, \"status\": \"3\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 58}, {\"key\": 91, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 91\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 959, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 66}, {\"key\": 90, \"disabled\": true, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 90\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 958, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 72}, {\"key\": 89, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 89\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 301, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 2}, {\"key\": 88, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 88\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 277, \"status\": \"1\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 12}, {\"key\": 87, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 87\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 810, \"status\": \"1\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 82}, {\"key\": 86, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 86\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 780, \"status\": \"3\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 22}, {\"key\": 85, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 85\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 705, \"status\": \"3\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 12}, {\"key\": 84, \"disabled\": true, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 84\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 203, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 79}, {\"key\": 83, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 83\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 491, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 59}, {\"key\": 82, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 82\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 73, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 100}, {\"key\": 81, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 81\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 406, \"status\": \"3\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 61}, {\"key\": 80, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 80\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 112, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 20}] */\n        data: {\n      key: number,\ndisabled: boolean,\nhref: string,\navatar: string,\nname: string,\nowner: string,\ndesc: string,\ncallNo: number,\nstatus: string,\nupdatedAt: string,\ncreatedAt: string,\nprogress: number\n      }[];\n  /** example: 100 */\n        total: number;\n  /** example: true */\n        success: boolean;\n  /** example: 20 */\n        pageSize: number;\n  /** example: 1 */\n        current: number\n}\n  "
+  },
+  "POST /api/login/outLogin": {
+    "res": {
+      "data": {},
+      "success": true
+    },
+    "query": {
+      "token ": " 123"
+    },
+    "payload": {},
+    "types": "/** POST /api/login/outLogin */\nexport type POST_API_LOGIN_OUT_LOGIN_QUERY = {\n  /** example:  123 */\n        token : string\n}\n  \n\nexport type POST_API_LOGIN_OUT_LOGIN_PAYLOAD = {\n  \n}\n  \n\nexport type POST_API_LOGIN_OUT_LOGIN_RES = {\n  /** example: {} */\n        data: {\n      \n      };\n  /** example: true */\n        success: boolean\n}\n  "
+  },
+  "POST /api/login/account": {
+    "res": {
+      "status": "ok",
+      "type": "account",
+      "currentAuthority": "admin"
+    },
+    "query": {
+      "token ": " 123"
+    },
+    "payload": {
+      "username": "admin",
+      "password": "ant.design",
+      "autoLogin": true,
+      "type": "account"
+    },
+    "types": "/** POST /api/login/account */\nexport type POST_API_LOGIN_ACCOUNT_QUERY = {\n  /** example:  123 */\n        token : string\n}\n  \n\nexport type POST_API_LOGIN_ACCOUNT_PAYLOAD = {\n  /** example: admin */\n        username: string;\n  /** example: ant.design */\n        password: string;\n  /** example: true */\n        autoLogin: boolean;\n  /** example: account */\n        type: string\n}\n  \n\nexport type POST_API_LOGIN_ACCOUNT_RES = {\n  /** example: ok */\n        status: string;\n  /** example: account */\n        type: string;\n  /** example: admin */\n        currentAuthority: string\n}\n  "
+  }
+}
diff --git a/react-ui/types/cache/mock/login.mock.cache.js b/react-ui/types/cache/mock/login.mock.cache.js
new file mode 100644
index 0000000..6c59e19
--- /dev/null
+++ b/react-ui/types/cache/mock/login.mock.cache.js
@@ -0,0 +1,324 @@
+module.exports = {
+  'GET /api/currentUser': {
+    data: {
+      name: 'Serati Ma',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
+      userid: '00000001',
+      email: 'antdesign@alipay.com',
+      signature: '海纳百川,有容乃大',
+      title: '交互专家',
+      group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
+      tags: [
+        { key: '0', label: '很有想法的' },
+        { key: '1', label: '专注设计' },
+        { key: '2', label: '辣~' },
+        { key: '3', label: '大长腿' },
+        { key: '4', label: '川妹子' },
+        { key: '5', label: '海纳百川' },
+      ],
+      notifyCount: 12,
+      unreadCount: 11,
+      country: 'China',
+      geographic: {
+        province: { label: '浙江省', key: '330000' },
+        city: { label: '杭州市', key: '330100' },
+      },
+      address: '西湖区工专路 77 号',
+      phone: '0752-268888888',
+    },
+  },
+  'GET /api/rule': {
+    data: [
+      {
+        key: 99,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 99',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 503,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 81,
+      },
+      {
+        key: 98,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 98',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 164,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 12,
+      },
+      {
+        key: 97,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 97',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 174,
+        status: '1',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 81,
+      },
+      {
+        key: 96,
+        disabled: true,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 96',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 914,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 7,
+      },
+      {
+        key: 95,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 95',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 698,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 82,
+      },
+      {
+        key: 94,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 94',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 488,
+        status: '1',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 14,
+      },
+      {
+        key: 93,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 93',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 580,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 77,
+      },
+      {
+        key: 92,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 92',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 244,
+        status: '3',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 58,
+      },
+      {
+        key: 91,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 91',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 959,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 66,
+      },
+      {
+        key: 90,
+        disabled: true,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 90',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 958,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 72,
+      },
+      {
+        key: 89,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 89',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 301,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 2,
+      },
+      {
+        key: 88,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 88',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 277,
+        status: '1',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 12,
+      },
+      {
+        key: 87,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 87',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 810,
+        status: '1',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 82,
+      },
+      {
+        key: 86,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 86',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 780,
+        status: '3',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 22,
+      },
+      {
+        key: 85,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 85',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 705,
+        status: '3',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 12,
+      },
+      {
+        key: 84,
+        disabled: true,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 84',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 203,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 79,
+      },
+      {
+        key: 83,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 83',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 491,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 59,
+      },
+      {
+        key: 82,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 82',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 73,
+        status: '0',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 100,
+      },
+      {
+        key: 81,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+        name: 'TradeCode 81',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 406,
+        status: '3',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 61,
+      },
+      {
+        key: 80,
+        disabled: false,
+        href: 'https://ant.design',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+        name: 'TradeCode 80',
+        owner: '曲丽丽',
+        desc: '这是一段描述',
+        callNo: 112,
+        status: '2',
+        updatedAt: '2022-12-06T05:00:57.040Z',
+        createdAt: '2022-12-06T05:00:57.040Z',
+        progress: 20,
+      },
+    ],
+    total: 100,
+    success: true,
+    pageSize: 20,
+    current: 1,
+  },
+  'POST /api/login/outLogin': { data: {}, success: true },
+  'POST /api/login/account': {
+    status: 'ok',
+    type: 'account',
+    currentAuthority: 'admin',
+  },
+};
diff --git a/react-ui/types/cache/mock/mock.cache.js b/react-ui/types/cache/mock/mock.cache.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/react-ui/types/cache/mock/mock.cache.js
diff --git a/react-ui/types/index.d.ts b/react-ui/types/index.d.ts
new file mode 100644
index 0000000..2c2805a
--- /dev/null
+++ b/react-ui/types/index.d.ts
@@ -0,0 +1,120 @@
+export namespace API {
+  /** GET /api/currentUser */
+  export type GET_API_CURRENT_USER_QUERY = {
+    /** example:  123 */
+    token: string;
+  };
+
+  export type GET_API_CURRENT_USER_PAYLOAD = Record<string, any>;
+
+  export type GET_API_CURRENT_USER_RES = {
+    /** example: {"name": "Serati Ma", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png", "userid": "00000001", "email": "antdesign@alipay.com", "signature": "海纳百川,有容乃大", "title": "交互专家", "group": "蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED", "tags": [{"key": "0", "label": "很有想法的"}, {"key": "1", "label": "专注设计"}, {"key": "2", "label": "辣~"}, {"key": "3", "label": "大长腿"}, {"key": "4", "label": "川妹子"}, {"key": "5", "label": "海纳百川"}], "notifyCount": 12, "unreadCount": 11, "country": "China", "geographic": {"province": {"label": "浙江省", "key": "330000"}, "city": {"label": "杭州市", "key": "330100"}}, "address": "西湖区工专路 77 号", "phone": "0752-268888888"} */
+    data: {
+      name: string;
+      avatar: string;
+      userid: string;
+      email: string;
+      signature: string;
+      title: string;
+      group: string;
+      tags: {
+        key: string;
+        label: string;
+      }[];
+      notifyCount: number;
+      unreadCount: number;
+      country: string;
+      geographic: {
+        province: {
+          label: string;
+          key: string;
+        };
+        city: {
+          label: string;
+          key: string;
+        };
+      };
+      address: string;
+      phone: string;
+    };
+  };
+
+  /** GET /api/rule */
+  export type GET_API_RULE_QUERY = {
+    /** example:  123 */
+    token: string;
+    /** example: 1 */
+    current: string;
+    /** example: 20 */
+    pageSize: string;
+  };
+
+  export type GET_API_RULE_PAYLOAD = Record<string, any>;
+
+  export type GET_API_RULE_RES = {
+    /** example: [{"key": 99, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 99", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 503, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 81}, {"key": 98, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 98", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 164, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 12}, {"key": 97, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 97", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 174, "status": "1", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 81}, {"key": 96, "disabled": true, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 96", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 914, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 7}, {"key": 95, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 95", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 698, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 82}, {"key": 94, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 94", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 488, "status": "1", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 14}, {"key": 93, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 93", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 580, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 77}, {"key": 92, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 92", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 244, "status": "3", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 58}, {"key": 91, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 91", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 959, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 66}, {"key": 90, "disabled": true, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 90", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 958, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 72}, {"key": 89, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 89", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 301, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 2}, {"key": 88, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 88", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 277, "status": "1", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 12}, {"key": 87, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 87", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 810, "status": "1", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 82}, {"key": 86, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 86", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 780, "status": "3", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 22}, {"key": 85, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 85", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 705, "status": "3", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 12}, {"key": 84, "disabled": true, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 84", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 203, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 79}, {"key": 83, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 83", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 491, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 59}, {"key": 82, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 82", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 73, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 100}, {"key": 81, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 81", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 406, "status": "3", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 61}, {"key": 80, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 80", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 112, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 20}] */
+    data: {
+      key: number;
+      disabled: boolean;
+      href: string;
+      avatar: string;
+      name: string;
+      owner: string;
+      desc: string;
+      callNo: number;
+      status: string;
+      updatedAt: string;
+      createdAt: string;
+      progress: number;
+    }[];
+    /** example: 100 */
+    total: number;
+    /** example: true */
+    success: boolean;
+    /** example: 20 */
+    pageSize: number;
+    /** example: 1 */
+    current: number;
+  };
+
+  /** POST /api/login/outLogin */
+  export type POST_API_LOGIN_OUT_LOGIN_QUERY = {
+    /** example:  123 */
+    token: string;
+  };
+
+  export type POST_API_LOGIN_OUT_LOGIN_PAYLOAD = Record<string, any>;
+
+  export type POST_API_LOGIN_OUT_LOGIN_RES = {
+    /** example: {} */
+    data: Record<string, any>;
+    /** example: true */
+    success: boolean;
+  };
+
+  /** POST /api/login/account */
+  export type POST_API_LOGIN_ACCOUNT_QUERY = {
+    /** example:  123 */
+    token: string;
+  };
+
+  export type POST_API_LOGIN_ACCOUNT_PAYLOAD = {
+    /** example: admin */
+    username: string;
+    /** example: ant.design */
+    password: string;
+    /** example: true */
+    autoLogin: boolean;
+    /** example: account */
+    type: string;
+  };
+
+  export type POST_API_LOGIN_ACCOUNT_RES = {
+    /** example: ok */
+    status: string;
+    /** example: account */
+    type: string;
+    /** example: admin */
+    currentAuthority: string;
+  };
+}
diff --git a/react-ui/types/route.d.ts b/react-ui/types/route.d.ts
new file mode 100644
index 0000000..bf35123
--- /dev/null
+++ b/react-ui/types/route.d.ts
@@ -0,0 +1,18 @@
+declare namespace API {
+  type RoutersMenuItem = {
+    alwaysShow?: boolean;
+    children?: RoutersMenuItem[];
+    component?: string;
+    hidden?: boolean;
+    meta: MenuItemMeta;
+    name: string;
+    path: string;
+    redirect?: string;
+    [key: string]: any;
+  };
+  interface GetRoutersResult {
+    code: number;
+    msg: string;
+    data: RoutersMenuItem[];
+  }
+}
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
new file mode 100644
index 0000000..fa5532d
--- /dev/null
+++ b/ruoyi-admin/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.8.8</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>ruoyi-admin</artifactId>
+
+    <description>
+        web服务入口
+    </description>
+
+    <dependencies>
+
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+
+        <!-- spring-doc -->
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+        </dependency>
+
+         <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-framework</artifactId>
+        </dependency>
+
+        <!-- 定时任务-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-quartz</artifactId>
+        </dependency>
+
+        <!-- 代码生成-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-generator</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.5.15</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>   
+                <groupId>org.apache.maven.plugins</groupId>   
+                <artifactId>maven-war-plugin</artifactId>   
+                <version>3.1.0</version>   
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>   
+           </plugin>   
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
new file mode 100644
index 0000000..32eb6f1
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
@@ -0,0 +1,30 @@
+package com.ruoyi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+
+/**
+ * 启动程序
+ * 
+ * @author ruoyi
+ */
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+public class RuoYiApplication
+{
+    public static void main(String[] args)
+    {
+        // System.setProperty("spring.devtools.restart.enabled", "false");
+        SpringApplication.run(RuoYiApplication.class, args);
+        System.out.println("(♥◠‿◠)ノ゙  若依启动成功   ლ(´ڡ`ლ)゙  \n" +
+                " .-------.       ____     __        \n" +
+                " |  _ _   \\      \\   \\   /  /    \n" +
+                " | ( ' )  |       \\  _. /  '       \n" +
+                " |(_ o _) /        _( )_ .'         \n" +
+                " | (_,_).' __  ___(_ o _)'          \n" +
+                " |  |\\ \\  |  ||   |(_,_)'         \n" +
+                " |  | \\ `'   /|   `-'  /           \n" +
+                " |  |  \\    /  \\      /           \n" +
+                " ''-'   `'-'    `-..-'              ");
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java
new file mode 100644
index 0000000..6de67dc
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java
@@ -0,0 +1,18 @@
+package com.ruoyi;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ * 
+ * @author ruoyi
+ */
+public class RuoYiServletInitializer extends SpringBootServletInitializer
+{
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
+    {
+        return application.sources(RuoYiApplication.class);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerProjectController.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerProjectController.java
new file mode 100644
index 0000000..38cea63
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerProjectController.java
@@ -0,0 +1,104 @@
+package com.ruoyi.tracker.controller;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.tracker.domain.TrackerProject;
+import com.ruoyi.tracker.service.ITrackerProjectService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 项目Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+@RestController
+@RequestMapping("/tracker/project")
+public class TrackerProjectController extends BaseController
+{
+    @Autowired
+    private ITrackerProjectService trackerProjectService;
+
+    /**
+     * 查询项目列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:project:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TrackerProject trackerProject)
+    {
+        startPage();
+        List<TrackerProject> list = trackerProjectService.selectTrackerProjectList(trackerProject);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出项目列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:project:export')")
+    @Log(title = "项目", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, TrackerProject trackerProject)
+    {
+        List<TrackerProject> list = trackerProjectService.selectTrackerProjectList(trackerProject);
+        ExcelUtil<TrackerProject> util = new ExcelUtil<TrackerProject>(TrackerProject.class);
+        util.exportExcel(response, list, "项目数据");
+    }
+
+    /**
+     * 获取项目详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:project:query')")
+    @GetMapping(value = "/{projectId}")
+    public AjaxResult getInfo(@PathVariable("projectId") Long projectId)
+    {
+        return success(trackerProjectService.selectTrackerProjectByProjectId(projectId));
+    }
+
+    /**
+     * 新增项目
+     */
+    @PreAuthorize("@ss.hasPermi('system:project:add')")
+    @Log(title = "项目", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TrackerProject trackerProject)
+    {
+        return toAjax(trackerProjectService.insertTrackerProject(trackerProject));
+    }
+
+    /**
+     * 修改项目
+     */
+    @PreAuthorize("@ss.hasPermi('system:project:edit')")
+    @Log(title = "项目", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TrackerProject trackerProject)
+    {
+        return toAjax(trackerProjectService.updateTrackerProject(trackerProject));
+    }
+
+    /**
+     * 删除项目
+     */
+    @PreAuthorize("@ss.hasPermi('system:project:remove')")
+    @Log(title = "项目", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{projectIds}")
+    public AjaxResult remove(@PathVariable Long[] projectIds)
+    {
+        return toAjax(trackerProjectService.deleteTrackerProjectByProjectIds(projectIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerProjectUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerProjectUserController.java
new file mode 100644
index 0000000..09a4ac5
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerProjectUserController.java
@@ -0,0 +1,105 @@
+package com.ruoyi.tracker.controller;
+
+import java.util.List;
+
+import com.ruoyi.tracker.service.ITrackerProjectUserService;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.tracker.domain.TrackerProjectUser;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 项目与用户关联Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+@RestController
+@RequestMapping("/tracker/user")
+public class TrackerProjectUserController extends BaseController
+{
+    @Autowired
+    private ITrackerProjectUserService trackerProjectUserService;
+
+    /**
+     * 查询项目与用户关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TrackerProjectUser trackerProjectUser)
+    {
+        startPage();
+        List<TrackerProjectUser> list = trackerProjectUserService.selectTrackerProjectUserList(trackerProjectUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出项目与用户关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:export')")
+    @Log(title = "项目与用户关联", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, TrackerProjectUser trackerProjectUser)
+    {
+        List<TrackerProjectUser> list = trackerProjectUserService.selectTrackerProjectUserList(trackerProjectUser);
+        ExcelUtil<TrackerProjectUser> util = new ExcelUtil<TrackerProjectUser>(TrackerProjectUser.class);
+        util.exportExcel(response, list, "项目与用户关联数据");
+    }
+
+    /**
+     * 获取项目与用户关联详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:query')")
+    @GetMapping(value = "/{projectId}")
+    public AjaxResult getInfo(@PathVariable("projectId") Long projectId)
+    {
+        return success(trackerProjectUserService.selectTrackerProjectUserByProjectId(projectId));
+    }
+
+    /**
+     * 新增项目与用户关联
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:add')")
+    @Log(title = "项目与用户关联", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TrackerProjectUser trackerProjectUser)
+    {
+        return toAjax(trackerProjectUserService.insertTrackerProjectUser(trackerProjectUser));
+    }
+
+    /**
+     * 修改项目与用户关联
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:edit')")
+    @Log(title = "项目与用户关联", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TrackerProjectUser trackerProjectUser)
+    {
+        return toAjax(trackerProjectUserService.updateTrackerProjectUser(trackerProjectUser));
+    }
+
+    /**
+     * 删除项目与用户关联
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:remove')")
+    @Log(title = "项目与用户关联", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{projectIds}")
+    public AjaxResult remove(@PathVariable Long[] projectIds)
+    {
+        return toAjax(trackerProjectUserService.deleteTrackerProjectUserByProjectIds(projectIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerTaskController.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerTaskController.java
new file mode 100644
index 0000000..d91ab84
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerTaskController.java
@@ -0,0 +1,104 @@
+package com.ruoyi.tracker.controller;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.tracker.domain.TrackerTask;
+import com.ruoyi.tracker.service.ITrackerTaskService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 任务Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+@RestController
+@RequestMapping("/tracker/task")
+public class TrackerTaskController extends BaseController
+{
+    @Autowired
+    private ITrackerTaskService trackerTaskService;
+
+    /**
+     * 查询任务列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:task:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TrackerTask trackerTask)
+    {
+        startPage();
+        List<TrackerTask> list = trackerTaskService.selectTrackerTaskList(trackerTask);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出任务列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:task:export')")
+    @Log(title = "任务", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, TrackerTask trackerTask)
+    {
+        List<TrackerTask> list = trackerTaskService.selectTrackerTaskList(trackerTask);
+        ExcelUtil<TrackerTask> util = new ExcelUtil<TrackerTask>(TrackerTask.class);
+        util.exportExcel(response, list, "任务数据");
+    }
+
+    /**
+     * 获取任务详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:task:query')")
+    @GetMapping(value = "/{taskId}")
+    public AjaxResult getInfo(@PathVariable("taskId") Long taskId)
+    {
+        return success(trackerTaskService.selectTrackerTaskByTaskId(taskId));
+    }
+
+    /**
+     * 新增任务
+     */
+    @PreAuthorize("@ss.hasPermi('system:task:add')")
+    @Log(title = "任务", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TrackerTask trackerTask)
+    {
+        return toAjax(trackerTaskService.insertTrackerTask(trackerTask));
+    }
+
+    /**
+     * 修改任务
+     */
+    @PreAuthorize("@ss.hasPermi('system:task:edit')")
+    @Log(title = "任务", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TrackerTask trackerTask)
+    {
+        return toAjax(trackerTaskService.updateTrackerTask(trackerTask));
+    }
+
+    /**
+     * 删除任务
+     */
+    @PreAuthorize("@ss.hasPermi('system:task:remove')")
+    @Log(title = "任务", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{taskIds}")
+    public AjaxResult remove(@PathVariable Long[] taskIds)
+    {
+        return toAjax(trackerTaskService.deleteTrackerTaskByTaskIds(taskIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerTaskLogController.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerTaskLogController.java
new file mode 100644
index 0000000..1b774b4
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/controller/TrackerTaskLogController.java
@@ -0,0 +1,104 @@
+package com.ruoyi.tracker.controller;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.tracker.domain.TrackerTaskLog;
+import com.ruoyi.tracker.service.ITrackerTaskLogService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 任务日志Controller
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+@RestController
+@RequestMapping("/tracker/log")
+public class TrackerTaskLogController extends BaseController
+{
+    @Autowired
+    private ITrackerTaskLogService trackerTaskLogService;
+
+    /**
+     * 查询任务日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:log:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TrackerTaskLog trackerTaskLog)
+    {
+        startPage();
+        List<TrackerTaskLog> list = trackerTaskLogService.selectTrackerTaskLogList(trackerTaskLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出任务日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:log:export')")
+    @Log(title = "任务日志", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, TrackerTaskLog trackerTaskLog)
+    {
+        List<TrackerTaskLog> list = trackerTaskLogService.selectTrackerTaskLogList(trackerTaskLog);
+        ExcelUtil<TrackerTaskLog> util = new ExcelUtil<TrackerTaskLog>(TrackerTaskLog.class);
+        util.exportExcel(response, list, "任务日志数据");
+    }
+
+    /**
+     * 获取任务日志详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:log:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return success(trackerTaskLogService.selectTrackerTaskLogByLogId(logId));
+    }
+
+    /**
+     * 新增任务日志
+     */
+    @PreAuthorize("@ss.hasPermi('system:log:add')")
+    @Log(title = "任务日志", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TrackerTaskLog trackerTaskLog)
+    {
+        return toAjax(trackerTaskLogService.insertTrackerTaskLog(trackerTaskLog));
+    }
+
+    /**
+     * 修改任务日志
+     */
+    @PreAuthorize("@ss.hasPermi('system:log:edit')")
+    @Log(title = "任务日志", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TrackerTaskLog trackerTaskLog)
+    {
+        return toAjax(trackerTaskLogService.updateTrackerTaskLog(trackerTaskLog));
+    }
+
+    /**
+     * 删除任务日志
+     */
+    @PreAuthorize("@ss.hasPermi('system:log:remove')")
+    @Log(title = "任务日志", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logIds}")
+    public AjaxResult remove(@PathVariable Long[] logIds)
+    {
+        return toAjax(trackerTaskLogService.deleteTrackerTaskLogByLogIds(logIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerProject.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerProject.java
new file mode 100644
index 0000000..a64a389
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerProject.java
@@ -0,0 +1,83 @@
+package com.ruoyi.tracker.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 项目对象 tracker_project
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public class TrackerProject extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 项目ID */
+    private Long projectId;
+
+    /** 项目名称 */
+    @Excel(name = "项目名称")
+    private String projectName;
+
+    /** 项目描述 */
+    @Excel(name = "项目描述")
+    private String description;
+
+    /** 项目状态(active: 激活, inactive: 不活跃) */
+    @Excel(name = "项目状态", readConverterExp = "a=ctive:,激=活,,i=nactive:,不=活跃")
+    private String status;
+
+    public void setProjectId(Long projectId) 
+    {
+        this.projectId = projectId;
+    }
+
+    public Long getProjectId() 
+    {
+        return projectId;
+    }
+    public void setProjectName(String projectName) 
+    {
+        this.projectName = projectName;
+    }
+
+    public String getProjectName() 
+    {
+        return projectName;
+    }
+    public void setDescription(String description) 
+    {
+        this.description = description;
+    }
+
+    public String getDescription() 
+    {
+        return description;
+    }
+    public void setStatus(String status) 
+    {
+        this.status = status;
+    }
+
+    public String getStatus() 
+    {
+        return status;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("projectId", getProjectId())
+            .append("projectName", getProjectName())
+            .append("description", getDescription())
+            .append("status", getStatus())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerProjectUser.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerProjectUser.java
new file mode 100644
index 0000000..a72ce55
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerProjectUser.java
@@ -0,0 +1,65 @@
+package com.ruoyi.tracker.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 项目与用户关联对象 tracker_project_user
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public class TrackerProjectUser extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 项目ID */
+    private Long projectId;
+
+    /** 用户ID */
+    private Long userId;
+
+    /** 角色(管理员、成员等) */
+    @Excel(name = "角色", readConverterExp = "管=理员、成员等")
+    private String role;
+
+    public void setProjectId(Long projectId) 
+    {
+        this.projectId = projectId;
+    }
+
+    public Long getProjectId() 
+    {
+        return projectId;
+    }
+    public void setUserId(Long userId) 
+    {
+        this.userId = userId;
+    }
+
+    public Long getUserId() 
+    {
+        return userId;
+    }
+    public void setRole(String role) 
+    {
+        this.role = role;
+    }
+
+    public String getRole() 
+    {
+        return role;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("projectId", getProjectId())
+            .append("userId", getUserId())
+            .append("role", getRole())
+            .append("createTime", getCreateTime())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerTask.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerTask.java
new file mode 100644
index 0000000..4a12ce9
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerTask.java
@@ -0,0 +1,123 @@
+package com.ruoyi.tracker.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 任务对象 tracker_task
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public class TrackerTask extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 任务ID */
+    private Long taskId;
+
+    /** 所属项目ID */
+    @Excel(name = "所属项目ID")
+    private Long projectId;
+
+    /** 任务名称 */
+    @Excel(name = "任务名称")
+    private String taskName;
+
+    /** 任务描述 */
+    @Excel(name = "任务描述")
+    private String description;
+
+    /** 分配给的用户ID */
+    @Excel(name = "分配给的用户ID")
+    private Long assignedTo;
+
+    /** 任务状态(open: 待办, in_progress: 进行中, closed: 完成) */
+    @Excel(name = "任务状态", readConverterExp = "o=pen:,待=办,,i=n_progress:,进=行中,,c=losed:,完=成")
+    private String status;
+
+    /** 任务优先级(low, medium, high) */
+    @Excel(name = "任务优先级", readConverterExp = "l=ow,,m=edium,,h=igh")
+    private String priority;
+
+    public void setTaskId(Long taskId) 
+    {
+        this.taskId = taskId;
+    }
+
+    public Long getTaskId() 
+    {
+        return taskId;
+    }
+    public void setProjectId(Long projectId) 
+    {
+        this.projectId = projectId;
+    }
+
+    public Long getProjectId() 
+    {
+        return projectId;
+    }
+    public void setTaskName(String taskName) 
+    {
+        this.taskName = taskName;
+    }
+
+    public String getTaskName() 
+    {
+        return taskName;
+    }
+    public void setDescription(String description) 
+    {
+        this.description = description;
+    }
+
+    public String getDescription() 
+    {
+        return description;
+    }
+    public void setAssignedTo(Long assignedTo) 
+    {
+        this.assignedTo = assignedTo;
+    }
+
+    public Long getAssignedTo() 
+    {
+        return assignedTo;
+    }
+    public void setStatus(String status) 
+    {
+        this.status = status;
+    }
+
+    public String getStatus() 
+    {
+        return status;
+    }
+    public void setPriority(String priority) 
+    {
+        this.priority = priority;
+    }
+
+    public String getPriority() 
+    {
+        return priority;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("taskId", getTaskId())
+            .append("projectId", getProjectId())
+            .append("taskName", getTaskName())
+            .append("description", getDescription())
+            .append("assignedTo", getAssignedTo())
+            .append("status", getStatus())
+            .append("priority", getPriority())
+            .append("createTime", getCreateTime())
+            .append("updateTime", getUpdateTime())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerTaskLog.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerTaskLog.java
new file mode 100644
index 0000000..af58359
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/domain/TrackerTaskLog.java
@@ -0,0 +1,94 @@
+package com.ruoyi.tracker.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 任务日志对象 tracker_task_log
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public class TrackerTaskLog extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 日志ID */
+    private Long logId;
+
+    /** 任务ID */
+    @Excel(name = "任务ID")
+    private Long taskId;
+
+    /** 操作用户ID */
+    @Excel(name = "操作用户ID")
+    private Long userId;
+
+    /** 操作类型(创建、更新、删除) */
+    @Excel(name = "操作类型", readConverterExp = "创=建、更新、删除")
+    private String action;
+
+    /** 操作描述 */
+    @Excel(name = "操作描述")
+    private String description;
+
+    public void setLogId(Long logId) 
+    {
+        this.logId = logId;
+    }
+
+    public Long getLogId() 
+    {
+        return logId;
+    }
+    public void setTaskId(Long taskId) 
+    {
+        this.taskId = taskId;
+    }
+
+    public Long getTaskId() 
+    {
+        return taskId;
+    }
+    public void setUserId(Long userId) 
+    {
+        this.userId = userId;
+    }
+
+    public Long getUserId() 
+    {
+        return userId;
+    }
+    public void setAction(String action) 
+    {
+        this.action = action;
+    }
+
+    public String getAction() 
+    {
+        return action;
+    }
+    public void setDescription(String description) 
+    {
+        this.description = description;
+    }
+
+    public String getDescription() 
+    {
+        return description;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("logId", getLogId())
+            .append("taskId", getTaskId())
+            .append("userId", getUserId())
+            .append("action", getAction())
+            .append("description", getDescription())
+            .append("createTime", getCreateTime())
+            .toString();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerProjectMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerProjectMapper.java
new file mode 100644
index 0000000..97bff5f
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerProjectMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.tracker.mapper;
+
+import java.util.List;
+import com.ruoyi.tracker.domain.TrackerProject;
+
+/**
+ * 项目Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public interface TrackerProjectMapper 
+{
+    /**
+     * 查询项目
+     * 
+     * @param projectId 项目主键
+     * @return 项目
+     */
+    public TrackerProject selectTrackerProjectByProjectId(Long projectId);
+
+    /**
+     * 查询项目列表
+     * 
+     * @param trackerProject 项目
+     * @return 项目集合
+     */
+    public List<TrackerProject> selectTrackerProjectList(TrackerProject trackerProject);
+
+    /**
+     * 新增项目
+     * 
+     * @param trackerProject 项目
+     * @return 结果
+     */
+    public int insertTrackerProject(TrackerProject trackerProject);
+
+    /**
+     * 修改项目
+     * 
+     * @param trackerProject 项目
+     * @return 结果
+     */
+    public int updateTrackerProject(TrackerProject trackerProject);
+
+    /**
+     * 删除项目
+     * 
+     * @param projectId 项目主键
+     * @return 结果
+     */
+    public int deleteTrackerProjectByProjectId(Long projectId);
+
+    /**
+     * 批量删除项目
+     * 
+     * @param projectIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteTrackerProjectByProjectIds(Long[] projectIds);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerProjectUserMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerProjectUserMapper.java
new file mode 100644
index 0000000..12d1904
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerProjectUserMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.tracker.mapper;
+
+import java.util.List;
+import com.ruoyi.tracker.domain.TrackerProjectUser;
+
+/**
+ * 项目与用户关联Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public interface TrackerProjectUserMapper 
+{
+    /**
+     * 查询项目与用户关联
+     * 
+     * @param projectId 项目与用户关联主键
+     * @return 项目与用户关联
+     */
+    public TrackerProjectUser selectTrackerProjectUserByProjectId(Long projectId);
+
+    /**
+     * 查询项目与用户关联列表
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 项目与用户关联集合
+     */
+    public List<TrackerProjectUser> selectTrackerProjectUserList(TrackerProjectUser trackerProjectUser);
+
+    /**
+     * 新增项目与用户关联
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 结果
+     */
+    public int insertTrackerProjectUser(TrackerProjectUser trackerProjectUser);
+
+    /**
+     * 修改项目与用户关联
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 结果
+     */
+    public int updateTrackerProjectUser(TrackerProjectUser trackerProjectUser);
+
+    /**
+     * 删除项目与用户关联
+     * 
+     * @param projectId 项目与用户关联主键
+     * @return 结果
+     */
+    public int deleteTrackerProjectUserByProjectId(Long projectId);
+
+    /**
+     * 批量删除项目与用户关联
+     * 
+     * @param projectIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteTrackerProjectUserByProjectIds(Long[] projectIds);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerTaskLogMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerTaskLogMapper.java
new file mode 100644
index 0000000..331a9df
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerTaskLogMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.tracker.mapper;
+
+import java.util.List;
+import com.ruoyi.tracker.domain.TrackerTaskLog;
+
+/**
+ * 任务日志Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public interface TrackerTaskLogMapper 
+{
+    /**
+     * 查询任务日志
+     * 
+     * @param logId 任务日志主键
+     * @return 任务日志
+     */
+    public TrackerTaskLog selectTrackerTaskLogByLogId(Long logId);
+
+    /**
+     * 查询任务日志列表
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 任务日志集合
+     */
+    public List<TrackerTaskLog> selectTrackerTaskLogList(TrackerTaskLog trackerTaskLog);
+
+    /**
+     * 新增任务日志
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 结果
+     */
+    public int insertTrackerTaskLog(TrackerTaskLog trackerTaskLog);
+
+    /**
+     * 修改任务日志
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 结果
+     */
+    public int updateTrackerTaskLog(TrackerTaskLog trackerTaskLog);
+
+    /**
+     * 删除任务日志
+     * 
+     * @param logId 任务日志主键
+     * @return 结果
+     */
+    public int deleteTrackerTaskLogByLogId(Long logId);
+
+    /**
+     * 批量删除任务日志
+     * 
+     * @param logIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteTrackerTaskLogByLogIds(Long[] logIds);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerTaskMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerTaskMapper.java
new file mode 100644
index 0000000..f2fd4dc
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/mapper/TrackerTaskMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.tracker.mapper;
+
+import java.util.List;
+import com.ruoyi.tracker.domain.TrackerTask;
+
+/**
+ * 任务Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public interface TrackerTaskMapper 
+{
+    /**
+     * 查询任务
+     * 
+     * @param taskId 任务主键
+     * @return 任务
+     */
+    public TrackerTask selectTrackerTaskByTaskId(Long taskId);
+
+    /**
+     * 查询任务列表
+     * 
+     * @param trackerTask 任务
+     * @return 任务集合
+     */
+    public List<TrackerTask> selectTrackerTaskList(TrackerTask trackerTask);
+
+    /**
+     * 新增任务
+     * 
+     * @param trackerTask 任务
+     * @return 结果
+     */
+    public int insertTrackerTask(TrackerTask trackerTask);
+
+    /**
+     * 修改任务
+     * 
+     * @param trackerTask 任务
+     * @return 结果
+     */
+    public int updateTrackerTask(TrackerTask trackerTask);
+
+    /**
+     * 删除任务
+     * 
+     * @param taskId 任务主键
+     * @return 结果
+     */
+    public int deleteTrackerTaskByTaskId(Long taskId);
+
+    /**
+     * 批量删除任务
+     * 
+     * @param taskIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteTrackerTaskByTaskIds(Long[] taskIds);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerProjectService.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerProjectService.java
new file mode 100644
index 0000000..8aa7b94
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerProjectService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.tracker.service;
+
+import java.util.List;
+import com.ruoyi.tracker.domain.TrackerProject;
+
+/**
+ * 项目Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public interface ITrackerProjectService 
+{
+    /**
+     * 查询项目
+     * 
+     * @param projectId 项目主键
+     * @return 项目
+     */
+    public TrackerProject selectTrackerProjectByProjectId(Long projectId);
+
+    /**
+     * 查询项目列表
+     * 
+     * @param trackerProject 项目
+     * @return 项目集合
+     */
+    public List<TrackerProject> selectTrackerProjectList(TrackerProject trackerProject);
+
+    /**
+     * 新增项目
+     * 
+     * @param trackerProject 项目
+     * @return 结果
+     */
+    public int insertTrackerProject(TrackerProject trackerProject);
+
+    /**
+     * 修改项目
+     * 
+     * @param trackerProject 项目
+     * @return 结果
+     */
+    public int updateTrackerProject(TrackerProject trackerProject);
+
+    /**
+     * 批量删除项目
+     * 
+     * @param projectIds 需要删除的项目主键集合
+     * @return 结果
+     */
+    public int deleteTrackerProjectByProjectIds(Long[] projectIds);
+
+    /**
+     * 删除项目信息
+     * 
+     * @param projectId 项目主键
+     * @return 结果
+     */
+    public int deleteTrackerProjectByProjectId(Long projectId);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerProjectUserService.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerProjectUserService.java
new file mode 100644
index 0000000..76a078c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerProjectUserService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.tracker.service;
+
+import java.util.List;
+import com.ruoyi.tracker.domain.TrackerProjectUser;
+
+/**
+ * 项目与用户关联Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public interface ITrackerProjectUserService 
+{
+    /**
+     * 查询项目与用户关联
+     * 
+     * @param projectId 项目与用户关联主键
+     * @return 项目与用户关联
+     */
+    public TrackerProjectUser selectTrackerProjectUserByProjectId(Long projectId);
+
+    /**
+     * 查询项目与用户关联列表
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 项目与用户关联集合
+     */
+    public List<TrackerProjectUser> selectTrackerProjectUserList(TrackerProjectUser trackerProjectUser);
+
+    /**
+     * 新增项目与用户关联
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 结果
+     */
+    public int insertTrackerProjectUser(TrackerProjectUser trackerProjectUser);
+
+    /**
+     * 修改项目与用户关联
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 结果
+     */
+    public int updateTrackerProjectUser(TrackerProjectUser trackerProjectUser);
+
+    /**
+     * 批量删除项目与用户关联
+     * 
+     * @param projectIds 需要删除的项目与用户关联主键集合
+     * @return 结果
+     */
+    public int deleteTrackerProjectUserByProjectIds(Long[] projectIds);
+
+    /**
+     * 删除项目与用户关联信息
+     * 
+     * @param projectId 项目与用户关联主键
+     * @return 结果
+     */
+    public int deleteTrackerProjectUserByProjectId(Long projectId);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerTaskLogService.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerTaskLogService.java
new file mode 100644
index 0000000..6e8a55c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerTaskLogService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.tracker.service;
+
+import java.util.List;
+import com.ruoyi.tracker.domain.TrackerTaskLog;
+
+/**
+ * 任务日志Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public interface ITrackerTaskLogService 
+{
+    /**
+     * 查询任务日志
+     * 
+     * @param logId 任务日志主键
+     * @return 任务日志
+     */
+    public TrackerTaskLog selectTrackerTaskLogByLogId(Long logId);
+
+    /**
+     * 查询任务日志列表
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 任务日志集合
+     */
+    public List<TrackerTaskLog> selectTrackerTaskLogList(TrackerTaskLog trackerTaskLog);
+
+    /**
+     * 新增任务日志
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 结果
+     */
+    public int insertTrackerTaskLog(TrackerTaskLog trackerTaskLog);
+
+    /**
+     * 修改任务日志
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 结果
+     */
+    public int updateTrackerTaskLog(TrackerTaskLog trackerTaskLog);
+
+    /**
+     * 批量删除任务日志
+     * 
+     * @param logIds 需要删除的任务日志主键集合
+     * @return 结果
+     */
+    public int deleteTrackerTaskLogByLogIds(Long[] logIds);
+
+    /**
+     * 删除任务日志信息
+     * 
+     * @param logId 任务日志主键
+     * @return 结果
+     */
+    public int deleteTrackerTaskLogByLogId(Long logId);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerTaskService.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerTaskService.java
new file mode 100644
index 0000000..6bf8664
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/ITrackerTaskService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.tracker.service;
+
+import java.util.List;
+import com.ruoyi.tracker.domain.TrackerTask;
+
+/**
+ * 任务Service接口
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+public interface ITrackerTaskService 
+{
+    /**
+     * 查询任务
+     * 
+     * @param taskId 任务主键
+     * @return 任务
+     */
+    public TrackerTask selectTrackerTaskByTaskId(Long taskId);
+
+    /**
+     * 查询任务列表
+     * 
+     * @param trackerTask 任务
+     * @return 任务集合
+     */
+    public List<TrackerTask> selectTrackerTaskList(TrackerTask trackerTask);
+
+    /**
+     * 新增任务
+     * 
+     * @param trackerTask 任务
+     * @return 结果
+     */
+    public int insertTrackerTask(TrackerTask trackerTask);
+
+    /**
+     * 修改任务
+     * 
+     * @param trackerTask 任务
+     * @return 结果
+     */
+    public int updateTrackerTask(TrackerTask trackerTask);
+
+    /**
+     * 批量删除任务
+     * 
+     * @param taskIds 需要删除的任务主键集合
+     * @return 结果
+     */
+    public int deleteTrackerTaskByTaskIds(Long[] taskIds);
+
+    /**
+     * 删除任务信息
+     * 
+     * @param taskId 任务主键
+     * @return 结果
+     */
+    public int deleteTrackerTaskByTaskId(Long taskId);
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerProjectServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerProjectServiceImpl.java
new file mode 100644
index 0000000..fcf2639
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerProjectServiceImpl.java
@@ -0,0 +1,96 @@
+package com.ruoyi.tracker.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.tracker.mapper.TrackerProjectMapper;
+import com.ruoyi.tracker.domain.TrackerProject;
+import com.ruoyi.tracker.service.ITrackerProjectService;
+
+/**
+ * 项目Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+@Service
+public class TrackerProjectServiceImpl implements ITrackerProjectService 
+{
+    @Autowired
+    private TrackerProjectMapper trackerProjectMapper;
+
+    /**
+     * 查询项目
+     * 
+     * @param projectId 项目主键
+     * @return 项目
+     */
+    @Override
+    public TrackerProject selectTrackerProjectByProjectId(Long projectId)
+    {
+        return trackerProjectMapper.selectTrackerProjectByProjectId(projectId);
+    }
+
+    /**
+     * 查询项目列表
+     * 
+     * @param trackerProject 项目
+     * @return 项目
+     */
+    @Override
+    public List<TrackerProject> selectTrackerProjectList(TrackerProject trackerProject)
+    {
+        return trackerProjectMapper.selectTrackerProjectList(trackerProject);
+    }
+
+    /**
+     * 新增项目
+     * 
+     * @param trackerProject 项目
+     * @return 结果
+     */
+    @Override
+    public int insertTrackerProject(TrackerProject trackerProject)
+    {
+        trackerProject.setCreateTime(DateUtils.getNowDate());
+        return trackerProjectMapper.insertTrackerProject(trackerProject);
+    }
+
+    /**
+     * 修改项目
+     * 
+     * @param trackerProject 项目
+     * @return 结果
+     */
+    @Override
+    public int updateTrackerProject(TrackerProject trackerProject)
+    {
+        trackerProject.setUpdateTime(DateUtils.getNowDate());
+        return trackerProjectMapper.updateTrackerProject(trackerProject);
+    }
+
+    /**
+     * 批量删除项目
+     * 
+     * @param projectIds 需要删除的项目主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTrackerProjectByProjectIds(Long[] projectIds)
+    {
+        return trackerProjectMapper.deleteTrackerProjectByProjectIds(projectIds);
+    }
+
+    /**
+     * 删除项目信息
+     * 
+     * @param projectId 项目主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTrackerProjectByProjectId(Long projectId)
+    {
+        return trackerProjectMapper.deleteTrackerProjectByProjectId(projectId);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerProjectUserServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerProjectUserServiceImpl.java
new file mode 100644
index 0000000..39f31ab
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerProjectUserServiceImpl.java
@@ -0,0 +1,95 @@
+package com.ruoyi.tracker.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.tracker.mapper.TrackerProjectUserMapper;
+import com.ruoyi.tracker.domain.TrackerProjectUser;
+import com.ruoyi.tracker.service.ITrackerProjectUserService;
+
+/**
+ * 项目与用户关联Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+@Service
+public class TrackerProjectUserServiceImpl implements ITrackerProjectUserService 
+{
+    @Autowired
+    private TrackerProjectUserMapper trackerProjectUserMapper;
+
+    /**
+     * 查询项目与用户关联
+     * 
+     * @param projectId 项目与用户关联主键
+     * @return 项目与用户关联
+     */
+    @Override
+    public TrackerProjectUser selectTrackerProjectUserByProjectId(Long projectId)
+    {
+        return trackerProjectUserMapper.selectTrackerProjectUserByProjectId(projectId);
+    }
+
+    /**
+     * 查询项目与用户关联列表
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 项目与用户关联
+     */
+    @Override
+    public List<TrackerProjectUser> selectTrackerProjectUserList(TrackerProjectUser trackerProjectUser)
+    {
+        return trackerProjectUserMapper.selectTrackerProjectUserList(trackerProjectUser);
+    }
+
+    /**
+     * 新增项目与用户关联
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 结果
+     */
+    @Override
+    public int insertTrackerProjectUser(TrackerProjectUser trackerProjectUser)
+    {
+        trackerProjectUser.setCreateTime(DateUtils.getNowDate());
+        return trackerProjectUserMapper.insertTrackerProjectUser(trackerProjectUser);
+    }
+
+    /**
+     * 修改项目与用户关联
+     * 
+     * @param trackerProjectUser 项目与用户关联
+     * @return 结果
+     */
+    @Override
+    public int updateTrackerProjectUser(TrackerProjectUser trackerProjectUser)
+    {
+        return trackerProjectUserMapper.updateTrackerProjectUser(trackerProjectUser);
+    }
+
+    /**
+     * 批量删除项目与用户关联
+     * 
+     * @param projectIds 需要删除的项目与用户关联主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTrackerProjectUserByProjectIds(Long[] projectIds)
+    {
+        return trackerProjectUserMapper.deleteTrackerProjectUserByProjectIds(projectIds);
+    }
+
+    /**
+     * 删除项目与用户关联信息
+     * 
+     * @param projectId 项目与用户关联主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTrackerProjectUserByProjectId(Long projectId)
+    {
+        return trackerProjectUserMapper.deleteTrackerProjectUserByProjectId(projectId);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerTaskLogServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerTaskLogServiceImpl.java
new file mode 100644
index 0000000..f0cbb60
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerTaskLogServiceImpl.java
@@ -0,0 +1,95 @@
+package com.ruoyi.tracker.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.tracker.mapper.TrackerTaskLogMapper;
+import com.ruoyi.tracker.domain.TrackerTaskLog;
+import com.ruoyi.tracker.service.ITrackerTaskLogService;
+
+/**
+ * 任务日志Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+@Service
+public class TrackerTaskLogServiceImpl implements ITrackerTaskLogService 
+{
+    @Autowired
+    private TrackerTaskLogMapper trackerTaskLogMapper;
+
+    /**
+     * 查询任务日志
+     * 
+     * @param logId 任务日志主键
+     * @return 任务日志
+     */
+    @Override
+    public TrackerTaskLog selectTrackerTaskLogByLogId(Long logId)
+    {
+        return trackerTaskLogMapper.selectTrackerTaskLogByLogId(logId);
+    }
+
+    /**
+     * 查询任务日志列表
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 任务日志
+     */
+    @Override
+    public List<TrackerTaskLog> selectTrackerTaskLogList(TrackerTaskLog trackerTaskLog)
+    {
+        return trackerTaskLogMapper.selectTrackerTaskLogList(trackerTaskLog);
+    }
+
+    /**
+     * 新增任务日志
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 结果
+     */
+    @Override
+    public int insertTrackerTaskLog(TrackerTaskLog trackerTaskLog)
+    {
+        trackerTaskLog.setCreateTime(DateUtils.getNowDate());
+        return trackerTaskLogMapper.insertTrackerTaskLog(trackerTaskLog);
+    }
+
+    /**
+     * 修改任务日志
+     * 
+     * @param trackerTaskLog 任务日志
+     * @return 结果
+     */
+    @Override
+    public int updateTrackerTaskLog(TrackerTaskLog trackerTaskLog)
+    {
+        return trackerTaskLogMapper.updateTrackerTaskLog(trackerTaskLog);
+    }
+
+    /**
+     * 批量删除任务日志
+     * 
+     * @param logIds 需要删除的任务日志主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTrackerTaskLogByLogIds(Long[] logIds)
+    {
+        return trackerTaskLogMapper.deleteTrackerTaskLogByLogIds(logIds);
+    }
+
+    /**
+     * 删除任务日志信息
+     * 
+     * @param logId 任务日志主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTrackerTaskLogByLogId(Long logId)
+    {
+        return trackerTaskLogMapper.deleteTrackerTaskLogByLogId(logId);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerTaskServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerTaskServiceImpl.java
new file mode 100644
index 0000000..afdae3c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/tracker/service/impl/TrackerTaskServiceImpl.java
@@ -0,0 +1,96 @@
+package com.ruoyi.tracker.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.tracker.mapper.TrackerTaskMapper;
+import com.ruoyi.tracker.domain.TrackerTask;
+import com.ruoyi.tracker.service.ITrackerTaskService;
+
+/**
+ * 任务Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-04-20
+ */
+@Service
+public class TrackerTaskServiceImpl implements ITrackerTaskService 
+{
+    @Autowired
+    private TrackerTaskMapper trackerTaskMapper;
+
+    /**
+     * 查询任务
+     * 
+     * @param taskId 任务主键
+     * @return 任务
+     */
+    @Override
+    public TrackerTask selectTrackerTaskByTaskId(Long taskId)
+    {
+        return trackerTaskMapper.selectTrackerTaskByTaskId(taskId);
+    }
+
+    /**
+     * 查询任务列表
+     * 
+     * @param trackerTask 任务
+     * @return 任务
+     */
+    @Override
+    public List<TrackerTask> selectTrackerTaskList(TrackerTask trackerTask)
+    {
+        return trackerTaskMapper.selectTrackerTaskList(trackerTask);
+    }
+
+    /**
+     * 新增任务
+     * 
+     * @param trackerTask 任务
+     * @return 结果
+     */
+    @Override
+    public int insertTrackerTask(TrackerTask trackerTask)
+    {
+        trackerTask.setCreateTime(DateUtils.getNowDate());
+        return trackerTaskMapper.insertTrackerTask(trackerTask);
+    }
+
+    /**
+     * 修改任务
+     * 
+     * @param trackerTask 任务
+     * @return 结果
+     */
+    @Override
+    public int updateTrackerTask(TrackerTask trackerTask)
+    {
+        trackerTask.setUpdateTime(DateUtils.getNowDate());
+        return trackerTaskMapper.updateTrackerTask(trackerTask);
+    }
+
+    /**
+     * 批量删除任务
+     * 
+     * @param taskIds 需要删除的任务主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTrackerTaskByTaskIds(Long[] taskIds)
+    {
+        return trackerTaskMapper.deleteTrackerTaskByTaskIds(taskIds);
+    }
+
+    /**
+     * 删除任务信息
+     * 
+     * @param taskId 任务主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTrackerTaskByTaskId(Long taskId)
+    {
+        return trackerTaskMapper.deleteTrackerTaskByTaskId(taskId);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java
new file mode 100644
index 0000000..d6dcf06
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java
@@ -0,0 +1,94 @@
+package com.ruoyi.web.controller.common;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import jakarta.annotation.Resource;
+import javax.imageio.ImageIO;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.google.code.kaptcha.Producer;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.sign.Base64;
+import com.ruoyi.common.utils.uuid.IdUtils;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 验证码操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+public class CaptchaController
+{
+    @Resource(name = "captchaProducer")
+    private Producer captchaProducer;
+
+    @Resource(name = "captchaProducerMath")
+    private Producer captchaProducerMath;
+
+    @Autowired
+    private RedisCache redisCache;
+    
+    @Autowired
+    private ISysConfigService configService;
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/captchaImage")
+    public AjaxResult getCode(HttpServletResponse response) throws IOException
+    {
+        AjaxResult ajax = AjaxResult.success();
+        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        ajax.put("captchaEnabled", captchaEnabled);
+        if (!captchaEnabled)
+        {
+            return ajax;
+        }
+
+        // 保存验证码信息
+        String uuid = IdUtils.simpleUUID();
+        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
+
+        String capStr = null, code = null;
+        BufferedImage image = null;
+
+        // 生成验证码
+        String captchaType = RuoYiConfig.getCaptchaType();
+        if ("math".equals(captchaType))
+        {
+            String capText = captchaProducerMath.createText();
+            capStr = capText.substring(0, capText.lastIndexOf("@"));
+            code = capText.substring(capText.lastIndexOf("@") + 1);
+            image = captchaProducerMath.createImage(capStr);
+        }
+        else if ("char".equals(captchaType))
+        {
+            capStr = code = captchaProducer.createText();
+            image = captchaProducer.createImage(capStr);
+        }
+
+        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
+        // 转换流信息写出
+        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
+        try
+        {
+            ImageIO.write(image, "jpg", os);
+        }
+        catch (IOException e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+
+        ajax.put("uuid", uuid);
+        ajax.put("img", Base64.encode(os.toByteArray()));
+        return ajax;
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
new file mode 100644
index 0000000..b7fad0c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
@@ -0,0 +1,163 @@
+package com.ruoyi.web.controller.common;
+
+import java.util.ArrayList;
+import java.util.List;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.FileUploadUtils;
+import com.ruoyi.common.utils.file.FileUtils;
+import com.ruoyi.framework.config.ServerConfig;
+
+/**
+ * 通用请求处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/common")
+public class CommonController
+{
+    private static final Logger log = LoggerFactory.getLogger(CommonController.class);
+
+    @Autowired
+    private ServerConfig serverConfig;
+
+    private static final String FILE_DELIMETER = ",";
+
+    /**
+     * 通用下载请求
+     * 
+     * @param fileName 文件名称
+     * @param delete 是否删除
+     */
+    @GetMapping("/download")
+    public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
+    {
+        try
+        {
+            if (!FileUtils.checkAllowDownload(fileName))
+            {
+                throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
+            }
+            String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
+            String filePath = RuoYiConfig.getDownloadPath() + fileName;
+
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, realFileName);
+            FileUtils.writeBytes(filePath, response.getOutputStream());
+            if (delete)
+            {
+                FileUtils.deleteFile(filePath);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("下载文件失败", e);
+        }
+    }
+
+    /**
+     * 通用上传请求(单个)
+     */
+    @PostMapping("/upload")
+    public AjaxResult uploadFile(MultipartFile file) throws Exception
+    {
+        try
+        {
+            // 上传文件路径
+            String filePath = RuoYiConfig.getUploadPath();
+            // 上传并返回新文件名称
+            String fileName = FileUploadUtils.upload(filePath, file);
+            String url = serverConfig.getUrl() + fileName;
+            AjaxResult ajax = AjaxResult.success();
+            ajax.put("url", url);
+            ajax.put("fileName", fileName);
+            ajax.put("newFileName", FileUtils.getName(fileName));
+            ajax.put("originalFilename", file.getOriginalFilename());
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 通用上传请求(多个)
+     */
+    @PostMapping("/uploads")
+    public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception
+    {
+        try
+        {
+            // 上传文件路径
+            String filePath = RuoYiConfig.getUploadPath();
+            List<String> urls = new ArrayList<String>();
+            List<String> fileNames = new ArrayList<String>();
+            List<String> newFileNames = new ArrayList<String>();
+            List<String> originalFilenames = new ArrayList<String>();
+            for (MultipartFile file : files)
+            {
+                // 上传并返回新文件名称
+                String fileName = FileUploadUtils.upload(filePath, file);
+                String url = serverConfig.getUrl() + fileName;
+                urls.add(url);
+                fileNames.add(fileName);
+                newFileNames.add(FileUtils.getName(fileName));
+                originalFilenames.add(file.getOriginalFilename());
+            }
+            AjaxResult ajax = AjaxResult.success();
+            ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER));
+            ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER));
+            ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));
+            ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 本地资源通用下载
+     */
+    @GetMapping("/download/resource")
+    public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
+            throws Exception
+    {
+        try
+        {
+            if (!FileUtils.checkAllowDownload(resource))
+            {
+                throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource));
+            }
+            // 本地资源路径
+            String localPath = RuoYiConfig.getProfile();
+            // 数据库资源地址
+            String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
+            // 下载名称
+            String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, downloadName);
+            FileUtils.writeBytes(downloadPath, response.getOutputStream());
+        }
+        catch (Exception e)
+        {
+            log.error("下载文件失败", e);
+        }
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java
new file mode 100644
index 0000000..d17bd66
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java
@@ -0,0 +1,122 @@
+package com.ruoyi.web.controller.monitor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysCache;
+
+/**
+ * 缓存监控
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/cache")
+public class CacheController
+{
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+    private final static List<SysCache> caches = new ArrayList<SysCache>();
+    {
+        caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息"));
+        caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息"));
+        caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典"));
+        caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
+        caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交"));
+        caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理"));
+        caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
+    }
+
+    @SuppressWarnings("deprecation")
+    @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @GetMapping()
+    public AjaxResult getInfo() throws Exception
+    {
+        Properties info = (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.info());
+        Properties commandStats = (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.info("commandstats"));
+        Object dbSize = redisTemplate.execute((RedisCallback<Object>) connection -> connection.dbSize());
+
+        Map<String, Object> result = new HashMap<>(3);
+        result.put("info", info);
+        result.put("dbSize", dbSize);
+
+        List<Map<String, String>> pieList = new ArrayList<>();
+        commandStats.stringPropertyNames().forEach(key -> {
+            Map<String, String> data = new HashMap<>(2);
+            String property = commandStats.getProperty(key);
+            data.put("name", StringUtils.removeStart(key, "cmdstat_"));
+            data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
+            pieList.add(data);
+        });
+        result.put("commandStats", pieList);
+        return AjaxResult.success(result);
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @GetMapping("/getNames")
+    public AjaxResult cache()
+    {
+        return AjaxResult.success(caches);
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @GetMapping("/getKeys/{cacheName}")
+    public AjaxResult getCacheKeys(@PathVariable String cacheName)
+    {
+        Set<String> cacheKeys = redisTemplate.keys(cacheName + "*");
+        return AjaxResult.success(new TreeSet<>(cacheKeys));
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @GetMapping("/getValue/{cacheName}/{cacheKey}")
+    public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey)
+    {
+        String cacheValue = redisTemplate.opsForValue().get(cacheKey);
+        SysCache sysCache = new SysCache(cacheName, cacheKey, cacheValue);
+        return AjaxResult.success(sysCache);
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @DeleteMapping("/clearCacheName/{cacheName}")
+    public AjaxResult clearCacheName(@PathVariable String cacheName)
+    {
+        Collection<String> cacheKeys = redisTemplate.keys(cacheName + "*");
+        redisTemplate.delete(cacheKeys);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @DeleteMapping("/clearCacheKey/{cacheKey}")
+    public AjaxResult clearCacheKey(@PathVariable String cacheKey)
+    {
+        redisTemplate.delete(cacheKey);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:cache:list')")
+    @DeleteMapping("/clearCacheAll")
+    public AjaxResult clearCacheAll()
+    {
+        Collection<String> cacheKeys = redisTemplate.keys("*");
+        redisTemplate.delete(cacheKeys);
+        return AjaxResult.success();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java
new file mode 100644
index 0000000..cc805ad
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java
@@ -0,0 +1,27 @@
+package com.ruoyi.web.controller.monitor;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.framework.web.domain.Server;
+
+/**
+ * 服务器监控
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/server")
+public class ServerController
+{
+    @PreAuthorize("@ss.hasPermi('monitor:server:list')")
+    @GetMapping()
+    public AjaxResult getInfo() throws Exception
+    {
+        Server server = new Server();
+        server.copyTo();
+        return AjaxResult.success(server);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java
new file mode 100644
index 0000000..f6fac71
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java
@@ -0,0 +1,82 @@
+package com.ruoyi.web.controller.monitor;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.web.service.SysPasswordService;
+import com.ruoyi.system.domain.SysLogininfor;
+import com.ruoyi.system.service.ISysLogininforService;
+
+/**
+ * 系统访问记录
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/logininfor")
+public class SysLogininforController extends BaseController
+{
+    @Autowired
+    private ISysLogininforService logininforService;
+
+    @Autowired
+    private SysPasswordService passwordService;
+
+    @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysLogininfor logininfor)
+    {
+        startPage();
+        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
+        return getDataTable(list);
+    }
+
+    @Log(title = "登录日志", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysLogininfor logininfor)
+    {
+        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
+        ExcelUtil<SysLogininfor> util = new ExcelUtil<SysLogininfor>(SysLogininfor.class);
+        util.exportExcel(response, list, "登录日志");
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')")
+    @Log(title = "登录日志", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{infoIds}")
+    public AjaxResult remove(@PathVariable Long[] infoIds)
+    {
+        return toAjax(logininforService.deleteLogininforByIds(infoIds));
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')")
+    @Log(title = "登录日志", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/clean")
+    public AjaxResult clean()
+    {
+        logininforService.cleanLogininfor();
+        return success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')")
+    @Log(title = "账户解锁", businessType = BusinessType.OTHER)
+    @GetMapping("/unlock/{userName}")
+    public AjaxResult unlock(@PathVariable("userName") String userName)
+    {
+        passwordService.clearLoginRecordCache(userName);
+        return success();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java
new file mode 100644
index 0000000..f056d65
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java
@@ -0,0 +1,69 @@
+package com.ruoyi.web.controller.monitor;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysOperLog;
+import com.ruoyi.system.service.ISysOperLogService;
+
+/**
+ * 操作日志记录
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/operlog")
+public class SysOperlogController extends BaseController
+{
+    @Autowired
+    private ISysOperLogService operLogService;
+
+    @PreAuthorize("@ss.hasPermi('monitor:operlog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysOperLog operLog)
+    {
+        startPage();
+        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
+        return getDataTable(list);
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('monitor:operlog:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysOperLog operLog)
+    {
+        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
+        ExcelUtil<SysOperLog> util = new ExcelUtil<SysOperLog>(SysOperLog.class);
+        util.exportExcel(response, list, "操作日志");
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.DELETE)
+    @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')")
+    @DeleteMapping("/{operIds}")
+    public AjaxResult remove(@PathVariable Long[] operIds)
+    {
+        return toAjax(operLogService.deleteOperLogByIds(operIds));
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.CLEAN)
+    @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')")
+    @DeleteMapping("/clean")
+    public AjaxResult clean()
+    {
+        operLogService.cleanOperLog();
+        return success();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java
new file mode 100644
index 0000000..a442863
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java
@@ -0,0 +1,83 @@
+package com.ruoyi.web.controller.monitor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysUserOnline;
+import com.ruoyi.system.service.ISysUserOnlineService;
+
+/**
+ * 在线用户监控
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/online")
+public class SysUserOnlineController extends BaseController
+{
+    @Autowired
+    private ISysUserOnlineService userOnlineService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @PreAuthorize("@ss.hasPermi('monitor:online:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(String ipaddr, String userName)
+    {
+        Collection<String> keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*");
+        List<SysUserOnline> userOnlineList = new ArrayList<SysUserOnline>();
+        for (String key : keys)
+        {
+            LoginUser user = redisCache.getCacheObject(key);
+            if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName))
+            {
+                userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user));
+            }
+            else if (StringUtils.isNotEmpty(ipaddr))
+            {
+                userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user));
+            }
+            else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser()))
+            {
+                userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
+            }
+            else
+            {
+                userOnlineList.add(userOnlineService.loginUserToUserOnline(user));
+            }
+        }
+        Collections.reverse(userOnlineList);
+        userOnlineList.removeAll(Collections.singleton(null));
+        return getDataTable(userOnlineList);
+    }
+
+    /**
+     * 强退用户
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')")
+    @Log(title = "在线用户", businessType = BusinessType.FORCE)
+    @DeleteMapping("/{tokenId}")
+    public AjaxResult forceLogout(@PathVariable String tokenId)
+    {
+        redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId);
+        return success();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java
new file mode 100644
index 0000000..91a854f
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java
@@ -0,0 +1,133 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysConfig;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 参数配置 信息操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/config")
+public class SysConfigController extends BaseController
+{
+    @Autowired
+    private ISysConfigService configService;
+
+    /**
+     * 获取参数配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:config:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysConfig config)
+    {
+        startPage();
+        List<SysConfig> list = configService.selectConfigList(config);
+        return getDataTable(list);
+    }
+
+    @Log(title = "参数管理", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:config:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysConfig config)
+    {
+        List<SysConfig> list = configService.selectConfigList(config);
+        ExcelUtil<SysConfig> util = new ExcelUtil<SysConfig>(SysConfig.class);
+        util.exportExcel(response, list, "参数数据");
+    }
+
+    /**
+     * 根据参数编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:config:query')")
+    @GetMapping(value = "/{configId}")
+    public AjaxResult getInfo(@PathVariable Long configId)
+    {
+        return success(configService.selectConfigById(configId));
+    }
+
+    /**
+     * 根据参数键名查询参数值
+     */
+    @GetMapping(value = "/configKey/{configKey}")
+    public AjaxResult getConfigKey(@PathVariable String configKey)
+    {
+        return success(configService.selectConfigByKey(configKey));
+    }
+
+    /**
+     * 新增参数配置
+     */
+    @PreAuthorize("@ss.hasPermi('system:config:add')")
+    @Log(title = "参数管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysConfig config)
+    {
+        if (!configService.checkConfigKeyUnique(config))
+        {
+            return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        config.setCreateBy(getUsername());
+        return toAjax(configService.insertConfig(config));
+    }
+
+    /**
+     * 修改参数配置
+     */
+    @PreAuthorize("@ss.hasPermi('system:config:edit')")
+    @Log(title = "参数管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysConfig config)
+    {
+        if (!configService.checkConfigKeyUnique(config))
+        {
+            return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        config.setUpdateBy(getUsername());
+        return toAjax(configService.updateConfig(config));
+    }
+
+    /**
+     * 删除参数配置
+     */
+    @PreAuthorize("@ss.hasPermi('system:config:remove')")
+    @Log(title = "参数管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{configIds}")
+    public AjaxResult remove(@PathVariable Long[] configIds)
+    {
+        configService.deleteConfigByIds(configIds);
+        return success();
+    }
+
+    /**
+     * 刷新参数缓存
+     */
+    @PreAuthorize("@ss.hasPermi('system:config:remove')")
+    @Log(title = "参数管理", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public AjaxResult refreshCache()
+    {
+        configService.resetConfigCache();
+        return success();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
new file mode 100644
index 0000000..59e7588
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
@@ -0,0 +1,132 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysDeptService;
+
+/**
+ * 部门信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/dept")
+public class SysDeptController extends BaseController
+{
+    @Autowired
+    private ISysDeptService deptService;
+
+    /**
+     * 获取部门列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:dept:list')")
+    @GetMapping("/list")
+    public AjaxResult list(SysDept dept)
+    {
+        List<SysDept> depts = deptService.selectDeptList(dept);
+        return success(depts);
+    }
+
+    /**
+     * 查询部门列表(排除节点)
+     */
+    @PreAuthorize("@ss.hasPermi('system:dept:list')")
+    @GetMapping("/list/exclude/{deptId}")
+    public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId)
+    {
+        List<SysDept> depts = deptService.selectDeptList(new SysDept());
+        depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + ""));
+        return success(depts);
+    }
+
+    /**
+     * 根据部门编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:dept:query')")
+    @GetMapping(value = "/{deptId}")
+    public AjaxResult getInfo(@PathVariable Long deptId)
+    {
+        deptService.checkDeptDataScope(deptId);
+        return success(deptService.selectDeptById(deptId));
+    }
+
+    /**
+     * 新增部门
+     */
+    @PreAuthorize("@ss.hasPermi('system:dept:add')")
+    @Log(title = "部门管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysDept dept)
+    {
+        if (!deptService.checkDeptNameUnique(dept))
+        {
+            return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        }
+        dept.setCreateBy(getUsername());
+        return toAjax(deptService.insertDept(dept));
+    }
+
+    /**
+     * 修改部门
+     */
+    @PreAuthorize("@ss.hasPermi('system:dept:edit')")
+    @Log(title = "部门管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysDept dept)
+    {
+        Long deptId = dept.getDeptId();
+        deptService.checkDeptDataScope(deptId);
+        if (!deptService.checkDeptNameUnique(dept))
+        {
+            return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        }
+        else if (dept.getParentId().equals(deptId))
+        {
+            return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
+        }
+        else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0)
+        {
+            return error("该部门包含未停用的子部门!");
+        }
+        dept.setUpdateBy(getUsername());
+        return toAjax(deptService.updateDept(dept));
+    }
+
+    /**
+     * 删除部门
+     */
+    @PreAuthorize("@ss.hasPermi('system:dept:remove')")
+    @Log(title = "部门管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{deptId}")
+    public AjaxResult remove(@PathVariable Long deptId)
+    {
+        if (deptService.hasChildByDeptId(deptId))
+        {
+            return warn("存在下级部门,不允许删除");
+        }
+        if (deptService.checkDeptExistUser(deptId))
+        {
+            return warn("部门存在用户,不允许删除");
+        }
+        deptService.checkDeptDataScope(deptId);
+        return toAjax(deptService.deleteDeptById(deptId));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java
new file mode 100644
index 0000000..f65492b
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java
@@ -0,0 +1,121 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.ArrayList;
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDictDataService;
+import com.ruoyi.system.service.ISysDictTypeService;
+
+/**
+ * 数据字典信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/dict/data")
+public class SysDictDataController extends BaseController
+{
+    @Autowired
+    private ISysDictDataService dictDataService;
+
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
+    @PreAuthorize("@ss.hasPermi('system:dict:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysDictData dictData)
+    {
+        startPage();
+        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        return getDataTable(list);
+    }
+
+    @Log(title = "字典数据", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:dict:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysDictData dictData)
+    {
+        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        ExcelUtil<SysDictData> util = new ExcelUtil<SysDictData>(SysDictData.class);
+        util.exportExcel(response, list, "字典数据");
+    }
+
+    /**
+     * 查询字典数据详细
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:query')")
+    @GetMapping(value = "/{dictCode}")
+    public AjaxResult getInfo(@PathVariable Long dictCode)
+    {
+        return success(dictDataService.selectDictDataById(dictCode));
+    }
+
+    /**
+     * 根据字典类型查询字典数据信息
+     */
+    @GetMapping(value = "/type/{dictType}")
+    public AjaxResult dictType(@PathVariable String dictType)
+    {
+        List<SysDictData> data = dictTypeService.selectDictDataByType(dictType);
+        if (StringUtils.isNull(data))
+        {
+            data = new ArrayList<SysDictData>();
+        }
+        return success(data);
+    }
+
+    /**
+     * 新增字典类型
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:add')")
+    @Log(title = "字典数据", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysDictData dict)
+    {
+        dict.setCreateBy(getUsername());
+        return toAjax(dictDataService.insertDictData(dict));
+    }
+
+    /**
+     * 修改保存字典类型
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:edit')")
+    @Log(title = "字典数据", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysDictData dict)
+    {
+        dict.setUpdateBy(getUsername());
+        return toAjax(dictDataService.updateDictData(dict));
+    }
+
+    /**
+     * 删除字典类型
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:remove')")
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{dictCodes}")
+    public AjaxResult remove(@PathVariable Long[] dictCodes)
+    {
+        dictDataService.deleteDictDataByIds(dictCodes);
+        return success();
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java
new file mode 100644
index 0000000..3ff314c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java
@@ -0,0 +1,131 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDictType;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDictTypeService;
+
+/**
+ * 数据字典信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/dict/type")
+public class SysDictTypeController extends BaseController
+{
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
+    @PreAuthorize("@ss.hasPermi('system:dict:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysDictType dictType)
+    {
+        startPage();
+        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        return getDataTable(list);
+    }
+
+    @Log(title = "字典类型", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:dict:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysDictType dictType)
+    {
+        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        ExcelUtil<SysDictType> util = new ExcelUtil<SysDictType>(SysDictType.class);
+        util.exportExcel(response, list, "字典类型");
+    }
+
+    /**
+     * 查询字典类型详细
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:query')")
+    @GetMapping(value = "/{dictId}")
+    public AjaxResult getInfo(@PathVariable Long dictId)
+    {
+        return success(dictTypeService.selectDictTypeById(dictId));
+    }
+
+    /**
+     * 新增字典类型
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:add')")
+    @Log(title = "字典类型", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysDictType dict)
+    {
+        if (!dictTypeService.checkDictTypeUnique(dict))
+        {
+            return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dict.setCreateBy(getUsername());
+        return toAjax(dictTypeService.insertDictType(dict));
+    }
+
+    /**
+     * 修改字典类型
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:edit')")
+    @Log(title = "字典类型", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysDictType dict)
+    {
+        if (!dictTypeService.checkDictTypeUnique(dict))
+        {
+            return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dict.setUpdateBy(getUsername());
+        return toAjax(dictTypeService.updateDictType(dict));
+    }
+
+    /**
+     * 删除字典类型
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:remove')")
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{dictIds}")
+    public AjaxResult remove(@PathVariable Long[] dictIds)
+    {
+        dictTypeService.deleteDictTypeByIds(dictIds);
+        return success();
+    }
+
+    /**
+     * 刷新字典缓存
+     */
+    @PreAuthorize("@ss.hasPermi('system:dict:remove')")
+    @Log(title = "字典类型", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/refreshCache")
+    public AjaxResult refreshCache()
+    {
+        dictTypeService.resetDictCache();
+        return success();
+    }
+
+    /**
+     * 获取字典选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll();
+        return success(dictTypes);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
new file mode 100644
index 0000000..13007eb
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
@@ -0,0 +1,29 @@
+package com.ruoyi.web.controller.system;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 首页
+ *
+ * @author ruoyi
+ */
+@RestController
+public class SysIndexController
+{
+    /** 系统基础配置 */
+    @Autowired
+    private RuoYiConfig ruoyiConfig;
+
+    /**
+     * 访问首页,提示语
+     */
+    @RequestMapping("/")
+    public String index()
+    {
+        return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
new file mode 100644
index 0000000..d959a17
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
@@ -0,0 +1,86 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import java.util.Set;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginBody;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.framework.web.service.SysLoginService;
+import com.ruoyi.framework.web.service.SysPermissionService;
+import com.ruoyi.system.service.ISysMenuService;
+
+/**
+ * 登录验证
+ * 
+ * @author ruoyi
+ */
+@RestController
+public class SysLoginController
+{
+    @Autowired
+    private SysLoginService loginService;
+
+    @Autowired
+    private ISysMenuService menuService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    /**
+     * 登录方法
+     * 
+     * @param loginBody 登录信息
+     * @return 结果
+     */
+    @PostMapping("/login")
+    public AjaxResult login(@RequestBody LoginBody loginBody)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        // 生成令牌
+        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
+                loginBody.getUuid());
+        ajax.put(Constants.TOKEN, token);
+        return ajax;
+    }
+
+    /**
+     * 获取用户信息
+     * 
+     * @return 用户信息
+     */
+    @GetMapping("getInfo")
+    public AjaxResult getInfo()
+    {
+        SysUser user = SecurityUtils.getLoginUser().getUser();
+        // 角色集合
+        Set<String> roles = permissionService.getRolePermission(user);
+        // 权限集合
+        Set<String> permissions = permissionService.getMenuPermission(user);
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("user", user);
+        ajax.put("roles", roles);
+        ajax.put("permissions", permissions);
+        return ajax;
+    }
+
+    /**
+     * 获取路由信息
+     * 
+     * @return 路由信息
+     */
+    @GetMapping("getRouters")
+    public AjaxResult getRouters()
+    {
+        Long userId = SecurityUtils.getUserId();
+        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
+        return AjaxResult.success(menuService.buildMenus(menus));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
new file mode 100644
index 0000000..03b6b65
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
@@ -0,0 +1,142 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysMenuService;
+
+/**
+ * 菜单信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/menu")
+public class SysMenuController extends BaseController
+{
+    @Autowired
+    private ISysMenuService menuService;
+
+    /**
+     * 获取菜单列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:menu:list')")
+    @GetMapping("/list")
+    public AjaxResult list(SysMenu menu)
+    {
+        List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
+        return success(menus);
+    }
+
+    /**
+     * 根据菜单编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:menu:query')")
+    @GetMapping(value = "/{menuId}")
+    public AjaxResult getInfo(@PathVariable Long menuId)
+    {
+        return success(menuService.selectMenuById(menuId));
+    }
+
+    /**
+     * 获取菜单下拉树列表
+     */
+    @GetMapping("/treeselect")
+    public AjaxResult treeselect(SysMenu menu)
+    {
+        List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
+        return success(menuService.buildMenuTreeSelect(menus));
+    }
+
+    /**
+     * 加载对应角色菜单列表树
+     */
+    @GetMapping(value = "/roleMenuTreeselect/{roleId}")
+    public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId)
+    {
+        List<SysMenu> menus = menuService.selectMenuList(getUserId());
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId));
+        ajax.put("menus", menuService.buildMenuTreeSelect(menus));
+        return ajax;
+    }
+
+    /**
+     * 新增菜单
+     */
+    @PreAuthorize("@ss.hasPermi('system:menu:add')")
+    @Log(title = "菜单管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysMenu menu)
+    {
+        if (!menuService.checkMenuNameUnique(menu))
+        {
+            return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        }
+        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
+        {
+            return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        }
+        menu.setCreateBy(getUsername());
+        return toAjax(menuService.insertMenu(menu));
+    }
+
+    /**
+     * 修改菜单
+     */
+    @PreAuthorize("@ss.hasPermi('system:menu:edit')")
+    @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysMenu menu)
+    {
+        if (!menuService.checkMenuNameUnique(menu))
+        {
+            return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        }
+        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
+        {
+            return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
+        }
+        else if (menu.getMenuId().equals(menu.getParentId()))
+        {
+            return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
+        }
+        menu.setUpdateBy(getUsername());
+        return toAjax(menuService.updateMenu(menu));
+    }
+
+    /**
+     * 删除菜单
+     */
+    @PreAuthorize("@ss.hasPermi('system:menu:remove')")
+    @Log(title = "菜单管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{menuId}")
+    public AjaxResult remove(@PathVariable("menuId") Long menuId)
+    {
+        if (menuService.hasChildByMenuId(menuId))
+        {
+            return warn("存在子菜单,不允许删除");
+        }
+        if (menuService.checkMenuExistRole(menuId))
+        {
+            return warn("菜单已分配,不允许删除");
+        }
+        return toAjax(menuService.deleteMenuById(menuId));
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java
new file mode 100644
index 0000000..8622828
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java
@@ -0,0 +1,91 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.SysNotice;
+import com.ruoyi.system.service.ISysNoticeService;
+
+/**
+ * 公告 信息操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/notice")
+public class SysNoticeController extends BaseController
+{
+    @Autowired
+    private ISysNoticeService noticeService;
+
+    /**
+     * 获取通知公告列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:notice:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysNotice notice)
+    {
+        startPage();
+        List<SysNotice> list = noticeService.selectNoticeList(notice);
+        return getDataTable(list);
+    }
+
+    /**
+     * 根据通知公告编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:notice:query')")
+    @GetMapping(value = "/{noticeId}")
+    public AjaxResult getInfo(@PathVariable Long noticeId)
+    {
+        return success(noticeService.selectNoticeById(noticeId));
+    }
+
+    /**
+     * 新增通知公告
+     */
+    @PreAuthorize("@ss.hasPermi('system:notice:add')")
+    @Log(title = "通知公告", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysNotice notice)
+    {
+        notice.setCreateBy(getUsername());
+        return toAjax(noticeService.insertNotice(notice));
+    }
+
+    /**
+     * 修改通知公告
+     */
+    @PreAuthorize("@ss.hasPermi('system:notice:edit')")
+    @Log(title = "通知公告", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysNotice notice)
+    {
+        notice.setUpdateBy(getUsername());
+        return toAjax(noticeService.updateNotice(notice));
+    }
+
+    /**
+     * 删除通知公告
+     */
+    @PreAuthorize("@ss.hasPermi('system:notice:remove')")
+    @Log(title = "通知公告", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{noticeIds}")
+    public AjaxResult remove(@PathVariable Long[] noticeIds)
+    {
+        return toAjax(noticeService.deleteNoticeByIds(noticeIds));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java
new file mode 100644
index 0000000..5b604cf
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java
@@ -0,0 +1,129 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysPost;
+import com.ruoyi.system.service.ISysPostService;
+
+/**
+ * 岗位信息操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/post")
+public class SysPostController extends BaseController
+{
+    @Autowired
+    private ISysPostService postService;
+
+    /**
+     * 获取岗位列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:post:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysPost post)
+    {
+        startPage();
+        List<SysPost> list = postService.selectPostList(post);
+        return getDataTable(list);
+    }
+    
+    @Log(title = "岗位管理", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:post:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysPost post)
+    {
+        List<SysPost> list = postService.selectPostList(post);
+        ExcelUtil<SysPost> util = new ExcelUtil<SysPost>(SysPost.class);
+        util.exportExcel(response, list, "岗位数据");
+    }
+
+    /**
+     * 根据岗位编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:post:query')")
+    @GetMapping(value = "/{postId}")
+    public AjaxResult getInfo(@PathVariable Long postId)
+    {
+        return success(postService.selectPostById(postId));
+    }
+
+    /**
+     * 新增岗位
+     */
+    @PreAuthorize("@ss.hasPermi('system:post:add')")
+    @Log(title = "岗位管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysPost post)
+    {
+        if (!postService.checkPostNameUnique(post))
+        {
+            return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
+        }
+        else if (!postService.checkPostCodeUnique(post))
+        {
+            return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
+        }
+        post.setCreateBy(getUsername());
+        return toAjax(postService.insertPost(post));
+    }
+
+    /**
+     * 修改岗位
+     */
+    @PreAuthorize("@ss.hasPermi('system:post:edit')")
+    @Log(title = "岗位管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysPost post)
+    {
+        if (!postService.checkPostNameUnique(post))
+        {
+            return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
+        }
+        else if (!postService.checkPostCodeUnique(post))
+        {
+            return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
+        }
+        post.setUpdateBy(getUsername());
+        return toAjax(postService.updatePost(post));
+    }
+
+    /**
+     * 删除岗位
+     */
+    @PreAuthorize("@ss.hasPermi('system:post:remove')")
+    @Log(title = "岗位管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{postIds}")
+    public AjaxResult remove(@PathVariable Long[] postIds)
+    {
+        return toAjax(postService.deletePostByIds(postIds));
+    }
+
+    /**
+     * 获取岗位选择框列表
+     */
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        List<SysPost> posts = postService.selectPostAll();
+        return success(posts);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
new file mode 100644
index 0000000..d5faedd
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
@@ -0,0 +1,137 @@
+package com.ruoyi.web.controller.system;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.FileUploadUtils;
+import com.ruoyi.common.utils.file.MimeTypeUtils;
+import com.ruoyi.framework.web.service.TokenService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 个人信息 业务处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/user/profile")
+public class SysProfileController extends BaseController
+{
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 个人信息
+     */
+    @GetMapping
+    public AjaxResult profile()
+    {
+        LoginUser loginUser = getLoginUser();
+        SysUser user = loginUser.getUser();
+        AjaxResult ajax = AjaxResult.success(user);
+        ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername()));
+        ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername()));
+        return ajax;
+    }
+
+    /**
+     * 修改用户
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult updateProfile(@RequestBody SysUser user)
+    {
+        LoginUser loginUser = getLoginUser();
+        SysUser currentUser = loginUser.getUser();
+        currentUser.setNickName(user.getNickName());
+        currentUser.setEmail(user.getEmail());
+        currentUser.setPhonenumber(user.getPhonenumber());
+        currentUser.setSex(user.getSex());
+        if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser))
+        {
+            return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在");
+        }
+        if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser))
+        {
+            return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在");
+        }
+        if (userService.updateUserProfile(currentUser) > 0)
+        {
+            // 更新缓存用户信息
+            tokenService.setLoginUser(loginUser);
+            return success();
+        }
+        return error("修改个人信息异常,请联系管理员");
+    }
+
+    /**
+     * 重置密码
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PutMapping("/updatePwd")
+    public AjaxResult updatePwd(String oldPassword, String newPassword)
+    {
+        LoginUser loginUser = getLoginUser();
+        String userName = loginUser.getUsername();
+        String password = loginUser.getPassword();
+        if (!SecurityUtils.matchesPassword(oldPassword, password))
+        {
+            return error("修改密码失败,旧密码错误");
+        }
+        if (SecurityUtils.matchesPassword(newPassword, password))
+        {
+            return error("新密码不能与旧密码相同");
+        }
+        newPassword = SecurityUtils.encryptPassword(newPassword);
+        if (userService.resetUserPwd(userName, newPassword) > 0)
+        {
+            // 更新缓存用户密码
+            loginUser.getUser().setPassword(newPassword);
+            tokenService.setLoginUser(loginUser);
+            return success();
+        }
+        return error("修改密码异常,请联系管理员");
+    }
+
+    /**
+     * 头像上传
+     */
+    @Log(title = "用户头像", businessType = BusinessType.UPDATE)
+    @PostMapping("/avatar")
+    public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception
+    {
+        if (!file.isEmpty())
+        {
+            LoginUser loginUser = getLoginUser();
+            String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);
+            if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
+            {
+                AjaxResult ajax = AjaxResult.success();
+                ajax.put("imgUrl", avatar);
+                // 更新缓存用户头像
+                loginUser.getUser().setAvatar(avatar);
+                tokenService.setLoginUser(loginUser);
+                return ajax;
+            }
+        }
+        return error("上传图片异常,请联系管理员");
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java
new file mode 100644
index 0000000..fe19249
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java
@@ -0,0 +1,38 @@
+package com.ruoyi.web.controller.system;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.model.RegisterBody;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.web.service.SysRegisterService;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 注册验证
+ * 
+ * @author ruoyi
+ */
+@RestController
+public class SysRegisterController extends BaseController
+{
+    @Autowired
+    private SysRegisterService registerService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @PostMapping("/register")
+    public AjaxResult register(@RequestBody RegisterBody user)
+    {
+        if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser"))))
+        {
+            return error("当前系统没有开启注册功能!");
+        }
+        String msg = registerService.register(user);
+        return StringUtils.isEmpty(msg) ? success() : error(msg);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
new file mode 100644
index 0000000..c8e7ab6
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
@@ -0,0 +1,262 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.web.service.SysPermissionService;
+import com.ruoyi.framework.web.service.TokenService;
+import com.ruoyi.system.domain.SysUserRole;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 角色信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/role")
+public class SysRoleController extends BaseController
+{
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysDeptService deptService;
+
+    @PreAuthorize("@ss.hasPermi('system:role:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysRole role)
+    {
+        startPage();
+        List<SysRole> list = roleService.selectRoleList(role);
+        return getDataTable(list);
+    }
+
+    @Log(title = "角色管理", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:role:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysRole role)
+    {
+        List<SysRole> list = roleService.selectRoleList(role);
+        ExcelUtil<SysRole> util = new ExcelUtil<SysRole>(SysRole.class);
+        util.exportExcel(response, list, "角色数据");
+    }
+
+    /**
+     * 根据角色编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:query')")
+    @GetMapping(value = "/{roleId}")
+    public AjaxResult getInfo(@PathVariable Long roleId)
+    {
+        roleService.checkRoleDataScope(roleId);
+        return success(roleService.selectRoleById(roleId));
+    }
+
+    /**
+     * 新增角色
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:add')")
+    @Log(title = "角色管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysRole role)
+    {
+        if (!roleService.checkRoleNameUnique(role))
+        {
+            return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
+        }
+        else if (!roleService.checkRoleKeyUnique(role))
+        {
+            return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
+        }
+        role.setCreateBy(getUsername());
+        return toAjax(roleService.insertRole(role));
+
+    }
+
+    /**
+     * 修改保存角色
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        if (!roleService.checkRoleNameUnique(role))
+        {
+            return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
+        }
+        else if (!roleService.checkRoleKeyUnique(role))
+        {
+            return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
+        }
+        role.setUpdateBy(getUsername());
+        
+        if (roleService.updateRole(role) > 0)
+        {
+            // 更新缓存用户权限
+            LoginUser loginUser = getLoginUser();
+            if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin())
+            {
+                loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName()));
+                loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
+                tokenService.setLoginUser(loginUser);
+            }
+            return success();
+        }
+        return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
+    }
+
+    /**
+     * 修改保存数据权限
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/dataScope")
+    public AjaxResult dataScope(@RequestBody SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        return toAjax(roleService.authDataScope(role));
+    }
+
+    /**
+     * 状态修改
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        roleService.checkRoleDataScope(role.getRoleId());
+        role.setUpdateBy(getUsername());
+        return toAjax(roleService.updateRoleStatus(role));
+    }
+
+    /**
+     * 删除角色
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:remove')")
+    @Log(title = "角色管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{roleIds}")
+    public AjaxResult remove(@PathVariable Long[] roleIds)
+    {
+        return toAjax(roleService.deleteRoleByIds(roleIds));
+    }
+
+    /**
+     * 获取角色选择框列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:query')")
+    @GetMapping("/optionselect")
+    public AjaxResult optionselect()
+    {
+        return success(roleService.selectRoleAll());
+    }
+
+    /**
+     * 查询已分配用户角色列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:list')")
+    @GetMapping("/authUser/allocatedList")
+    public TableDataInfo allocatedList(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectAllocatedList(user);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询未分配用户角色列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:list')")
+    @GetMapping("/authUser/unallocatedList")
+    public TableDataInfo unallocatedList(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectUnallocatedList(user);
+        return getDataTable(list);
+    }
+
+    /**
+     * 取消授权用户
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/cancel")
+    public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole)
+    {
+        return toAjax(roleService.deleteAuthUser(userRole));
+    }
+
+    /**
+     * 批量取消授权用户
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/cancelAll")
+    public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds)
+    {
+        return toAjax(roleService.deleteAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 批量选择用户授权
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:edit')")
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authUser/selectAll")
+    public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds)
+    {
+        roleService.checkRoleDataScope(roleId);
+        return toAjax(roleService.insertAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 获取对应角色部门树列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:role:query')")
+    @GetMapping(value = "/deptTree/{roleId}")
+    public AjaxResult deptTree(@PathVariable("roleId") Long roleId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId));
+        ajax.put("depts", deptService.selectDeptTreeList(new SysDept()));
+        return ajax;
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
new file mode 100644
index 0000000..96bd559
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
@@ -0,0 +1,256 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysPostService;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 用户信息
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/user")
+public class SysUserController extends BaseController
+{
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ISysDeptService deptService;
+
+    @Autowired
+    private ISysPostService postService;
+
+    /**
+     * 获取用户列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectUserList(user);
+        return getDataTable(list);
+    }
+
+    @Log(title = "用户管理", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('system:user:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysUser user)
+    {
+        List<SysUser> list = userService.selectUserList(user);
+        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
+        util.exportExcel(response, list, "用户数据");
+    }
+
+    @Log(title = "用户管理", businessType = BusinessType.IMPORT)
+    @PreAuthorize("@ss.hasPermi('system:user:import')")
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
+    {
+        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
+        List<SysUser> userList = util.importExcel(file.getInputStream());
+        String operName = getUsername();
+        String message = userService.importUser(userList, updateSupport, operName);
+        return success(message);
+    }
+
+    @PostMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response)
+    {
+        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
+        util.importTemplateExcel(response, "用户数据");
+    }
+
+    /**
+     * 根据用户编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:query')")
+    @GetMapping(value = { "/", "/{userId}" })
+    public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId)
+    {
+        userService.checkUserDataScope(userId);
+        AjaxResult ajax = AjaxResult.success();
+        List<SysRole> roles = roleService.selectRoleAll();
+        ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
+        ajax.put("posts", postService.selectPostAll());
+        if (StringUtils.isNotNull(userId))
+        {
+            SysUser sysUser = userService.selectUserById(userId);
+            ajax.put(AjaxResult.DATA_TAG, sysUser);
+            ajax.put("postIds", postService.selectPostListByUserId(userId));
+            ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList()));
+        }
+        return ajax;
+    }
+
+    /**
+     * 新增用户
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:add')")
+    @Log(title = "用户管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysUser user)
+    {
+        deptService.checkDeptDataScope(user.getDeptId());
+        roleService.checkRoleDataScope(user.getRoleIds());
+        if (!userService.checkUserNameUnique(user))
+        {
+            return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
+        }
+        else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
+        {
+            return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
+        }
+        else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
+        {
+            return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+        }
+        user.setCreateBy(getUsername());
+        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
+        return toAjax(userService.insertUser(user));
+    }
+
+    /**
+     * 修改用户
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        deptService.checkDeptDataScope(user.getDeptId());
+        roleService.checkRoleDataScope(user.getRoleIds());
+        if (!userService.checkUserNameUnique(user))
+        {
+            return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
+        }
+        else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user))
+        {
+            return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
+        }
+        else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user))
+        {
+            return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
+        }
+        user.setUpdateBy(getUsername());
+        return toAjax(userService.updateUser(user));
+    }
+
+    /**
+     * 删除用户
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:remove')")
+    @Log(title = "用户管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        if (ArrayUtils.contains(userIds, getUserId()))
+        {
+            return error("当前用户不能删除");
+        }
+        return toAjax(userService.deleteUserByIds(userIds));
+    }
+
+    /**
+     * 重置密码
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:resetPwd')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resetPwd")
+    public AjaxResult resetPwd(@RequestBody SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
+        user.setUpdateBy(getUsername());
+        return toAjax(userService.resetPwd(user));
+    }
+
+    /**
+     * 状态修改
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        userService.checkUserDataScope(user.getUserId());
+        user.setUpdateBy(getUsername());
+        return toAjax(userService.updateUserStatus(user));
+    }
+
+    /**
+     * 根据用户编号获取授权角色
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:query')")
+    @GetMapping("/authRole/{userId}")
+    public AjaxResult authRole(@PathVariable("userId") Long userId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        SysUser user = userService.selectUserById(userId);
+        List<SysRole> roles = roleService.selectRolesByUserId(userId);
+        ajax.put("user", user);
+        ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
+        return ajax;
+    }
+
+    /**
+     * 用户授权角色
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.GRANT)
+    @PutMapping("/authRole")
+    public AjaxResult insertAuthRole(Long userId, Long[] roleIds)
+    {
+        userService.checkUserDataScope(userId);
+        roleService.checkRoleDataScope(roleIds);
+        userService.insertUserAuth(userId, roleIds);
+        return success();
+    }
+
+    /**
+     * 获取部门树列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:user:list')")
+    @GetMapping("/deptTree")
+    public AjaxResult deptTree(SysDept dept)
+    {
+        return success(deptService.selectDeptTreeList(dept));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java
new file mode 100644
index 0000000..586e1b8
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java
@@ -0,0 +1,175 @@
+package com.ruoyi.web.controller.tool;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.utils.StringUtils;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * swagger 用户测试方法
+ *
+ * @author ruoyi
+ */
+@Tag(name = "用户信息管理")
+@RestController
+@RequestMapping("/test/user")
+public class TestController extends BaseController
+{
+    private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();
+    {
+        users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
+        users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
+    }
+    
+    @Operation(summary = "获取用户列表")
+    @GetMapping("/list")
+    public R<List<UserEntity>> userList()
+    {
+        List<UserEntity> userList = new ArrayList<UserEntity>(users.values());
+        return R.ok(userList);
+    }
+    
+    @Operation(summary = "获取用户详细")
+    @GetMapping("/{userId}")
+    public R<UserEntity> getUser(@PathVariable(name = "userId")
+    Integer userId)
+    {
+        if (!users.isEmpty() && users.containsKey(userId))
+        {
+            return R.ok(users.get(userId));
+        }
+        else
+        {
+            return R.fail("用户不存在");
+        }
+    }
+    
+    @Operation(summary = "新增用户")
+    @PostMapping("/save")
+    public R<String> save(UserEntity user)
+    {
+        if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
+        {
+            return R.fail("用户ID不能为空");
+        }
+        users.put(user.getUserId(), user);
+        return R.ok();
+    }
+    
+    @Operation(summary = "更新用户")
+    @PutMapping("/update")
+    public R<String> update(@RequestBody
+    UserEntity user)
+    {
+        if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
+        {
+            return R.fail("用户ID不能为空");
+        }
+        if (users.isEmpty() || !users.containsKey(user.getUserId()))
+        {
+            return R.fail("用户不存在");
+        }
+        users.remove(user.getUserId());
+        users.put(user.getUserId(), user);
+        return R.ok();
+    }
+    
+    @Operation(summary = "删除用户信息")
+    @DeleteMapping("/{userId}")
+    public R<String> delete(@PathVariable(name = "userId")
+    Integer userId)
+    {
+        if (!users.isEmpty() && users.containsKey(userId))
+        {
+            users.remove(userId);
+            return R.ok();
+        }
+        else
+        {
+            return R.fail("用户不存在");
+        }
+    }
+}
+
+@Schema(description = "用户实体")
+class UserEntity
+{
+    @Schema(title = "用户ID")
+    private Integer userId;
+    
+    @Schema(title = "用户名称")
+    private String username;
+    
+    @Schema(title = "用户密码")
+    private String password;
+    
+    @Schema(title = "用户手机")
+    private String mobile;
+    
+    public UserEntity()
+    {
+        
+    }
+    
+    public UserEntity(Integer userId, String username, String password, String mobile)
+    {
+        this.userId = userId;
+        this.username = username;
+        this.password = password;
+        this.mobile = mobile;
+    }
+    
+    public Integer getUserId()
+    {
+        return userId;
+    }
+    
+    public void setUserId(Integer userId)
+    {
+        this.userId = userId;
+    }
+    
+    public String getUsername()
+    {
+        return username;
+    }
+    
+    public void setUsername(String username)
+    {
+        this.username = username;
+    }
+    
+    public String getPassword()
+    {
+        return password;
+    }
+    
+    public void setPassword(String password)
+    {
+        this.password = password;
+    }
+    
+    public String getMobile()
+    {
+        return mobile;
+    }
+    
+    public void setMobile(String mobile)
+    {
+        this.mobile = mobile;
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java
new file mode 100644
index 0000000..9ab7c24
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java
@@ -0,0 +1,64 @@
+package com.ruoyi.web.core.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import com.ruoyi.common.config.RuoYiConfig;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+
+/**
+ * Swagger2的接口配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class SwaggerConfig
+{
+    /** 系统基础配置 */
+    @Autowired
+    private RuoYiConfig ruoyiConfig;
+    
+    /**
+     * 自定义的 OpenAPI 对象
+     */
+    @Bean
+    public OpenAPI customOpenApi()
+    {
+        return new OpenAPI().components(new Components()
+            // 设置认证的请求头
+            .addSecuritySchemes("apikey", securityScheme()))
+            .addSecurityItem(new SecurityRequirement().addList("apikey"))
+            .info(getApiInfo());
+    }
+    
+    @Bean
+    public SecurityScheme securityScheme()
+    {
+        return new SecurityScheme()
+            .type(SecurityScheme.Type.APIKEY)
+            .name("Authorization")
+            .in(SecurityScheme.In.HEADER)
+            .scheme("Bearer");
+    }
+    
+    /**
+     * 添加摘要信息
+     */
+    public Info getApiInfo()
+    {
+        return new Info()
+            // 设置标题
+            .title("标题:若依管理系统_接口文档")
+            // 描述
+            .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
+            // 作者信息
+            .contact(new Contact().name(ruoyiConfig.getName()))
+            // 版本
+            .version("版本号:" + ruoyiConfig.getVersion());
+    }
+}
diff --git a/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties
new file mode 100644
index 0000000..37e7b58
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties
@@ -0,0 +1 @@
+restart.include.json=/com.alibaba.fastjson2.*.jar
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml
new file mode 100644
index 0000000..12ed4d4
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/application-druid.yml
@@ -0,0 +1,61 @@
+# 数据源配置
+spring:
+    datasource:
+        type: com.alibaba.druid.pool.DruidDataSource
+        driverClassName: com.mysql.cj.jdbc.Driver
+        druid:
+            # 主库数据源
+            master:
+                url: jdbc:mysql://49.233.215.144:3306/pt_station?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                username: sy
+                password: sy_password
+            # 从库数据源
+            slave:
+                # 从数据源开关/默认关闭
+                enabled: false
+                url: 
+                username: 
+                password: 
+            # 初始连接数
+            initialSize: 5
+            # 最小连接池数量
+            minIdle: 10
+            # 最大连接池数量
+            maxActive: 20
+            # 配置获取连接等待超时的时间
+            maxWait: 60000
+            # 配置连接超时时间
+            connectTimeout: 30000
+            # 配置网络超时时间
+            socketTimeout: 60000
+            # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+            timeBetweenEvictionRunsMillis: 60000
+            # 配置一个连接在池中最小生存的时间,单位是毫秒
+            minEvictableIdleTimeMillis: 300000
+            # 配置一个连接在池中最大生存的时间,单位是毫秒
+            maxEvictableIdleTimeMillis: 900000
+            # 配置检测连接是否有效
+            validationQuery: SELECT 1 FROM DUAL
+            testWhileIdle: true
+            testOnBorrow: false
+            testOnReturn: false
+            webStatFilter: 
+                enabled: true
+            statViewServlet:
+                enabled: true
+                # 设置白名单,不填则允许所有访问
+                allow:
+                url-pattern: /druid/*
+                # 控制台管理用户名和密码
+                login-username: ruoyi
+                login-password: 123456
+            filter:
+                stat:
+                    enabled: true
+                    # 慢SQL记录
+                    log-slow-sql: true
+                    slow-sql-millis: 1000
+                    merge-sql: true
+                wall:
+                    config:
+                        multi-statement-allow: true
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
new file mode 100644
index 0000000..cb1a87c
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -0,0 +1,137 @@
+# 项目相关配置
+ruoyi:
+  # 名称
+  name: RuoYi
+  # 版本
+  version: 3.8.8
+  # 版权年份
+  copyrightYear: 2024
+  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
+  profile: D:/ruoyi/uploadPath
+  # 获取ip地址开关
+  addressEnabled: false
+  # 验证码类型 math 数字计算 char 字符验证
+  captchaType: math
+
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为8080
+  port: 8080
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  tomcat:
+    # tomcat的URI编码
+    uri-encoding: UTF-8
+    # 连接数满后的排队数,默认为100
+    accept-count: 1000
+    threads:
+      # tomcat最大线程数,默认为200
+      max: 800
+      # Tomcat启动初始化的线程数,默认值10
+      min-spare: 100
+
+# 日志配置
+logging:
+  level:
+    com.ruoyi: debug
+    org.springframework: warn
+
+# 用户配置
+user:
+  password:
+    # 密码最大错误次数
+    maxRetryCount: 5
+    # 密码锁定时间(默认10分钟)
+    lockTime: 10
+
+# Spring配置
+spring:
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: i18n/messages
+  profiles:
+    active: druid
+  # 文件上传
+  servlet:
+    multipart:
+      # 单个文件大小
+      max-file-size: 10MB
+      # 设置总上传的文件大小
+      max-request-size: 20MB
+  # 服务模块
+  devtools:
+    restart:
+      # 热部署开关
+      enabled: true
+  data:
+    # redis 配置
+    redis:
+      # 地址
+      host: localhost
+      # 端口,默认为6379
+      port: 6379
+      # 数据库索引
+      database: 0
+      # 密码
+      password:
+      # 连接超时时间
+      timeout: 10s
+      lettuce:
+        pool:
+          # 连接池中的最小空闲连接
+          min-idle: 0
+          # 连接池中的最大空闲连接
+          max-idle: 8
+          # 连接池的最大数据库连接数
+          max-active: 8
+          # #连接池最大阻塞等待时间(使用负值表示没有限制)
+          max-wait: -1ms
+
+# token配置
+token:
+  # 令牌自定义标识
+  header: Authorization
+  # 令牌密钥
+  secret: abcdefghijklmnopqrstuvwxyz
+  # 令牌有效期(默认30分钟)
+  expireTime: 30
+
+# MyBatis配置
+mybatis:
+  # 搜索指定包别名
+  typeAliasesPackage: com.ruoyi.**.domain
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 加载全局的配置文件
+  configLocation: classpath:mybatis/mybatis-config.xml
+
+# PageHelper分页插件
+pagehelper:
+  helperDialect: mysql
+  supportMethodsArguments: true
+  params: count=countSql
+
+# Springdoc配置
+springdoc:
+  api-docs:
+    path: /v3/api-docs
+  swagger-ui:
+    enabled: true
+    path: /swagger-ui.html
+    tags-sorter: alpha
+  group-configs:
+    - group: 'default'
+      display-name: '测试模块'
+      paths-to-match: '/**'
+      packages-to-scan: com.ruoyi.web.controller.tool
+
+# 防止XSS攻击
+xss:
+  # 过滤开关
+  enabled: true
+  # 排除链接(多个用逗号分隔)
+  excludes: /system/notice
+  # 匹配链接
+  urlPatterns: /system/*,/monitor/*,/tool/*
diff --git a/ruoyi-admin/src/main/resources/banner.txt b/ruoyi-admin/src/main/resources/banner.txt
new file mode 100644
index 0000000..0931cb8
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/banner.txt
@@ -0,0 +1,24 @@
+Application Version: ${ruoyi.version}
+Spring Boot Version: ${spring-boot.version}
+////////////////////////////////////////////////////////////////////
+//                          _ooOoo_                               //
+//                         o8888888o                              //
+//                         88" . "88                              //
+//                         (| ^_^ |)                              //
+//                         O\  =  /O                              //
+//                      ____/`---'\____                           //
+//                    .'  \\|     |//  `.                         //
+//                   /  \\|||  :  |||//  \                        //
+//                  /  _||||| -:- |||||-  \                       //
+//                  |   | \\\  -  /// |   |                       //
+//                  | \_|  ''\---/''  |   |                       //
+//                  \  .-\__  `-`  ___/-. /                       //
+//                ___`. .'  /--.--\  `. . ___                     //
+//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
+//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
+//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
+//      ========`-.____`-.___\_____/___.-`____.-'========         //
+//                           `=---='                              //
+//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
+//             佛祖保佑       永不宕机      永无BUG               //
+////////////////////////////////////////////////////////////////////
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties
new file mode 100644
index 0000000..93de005
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/i18n/messages.properties
@@ -0,0 +1,38 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=用户不存在/密码错误
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号已被删除
+user.blocked=用户已封禁,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+login.blocked=很遗憾,访问IP已被列入系统黑名单
+user.logout.success=退出成功
+
+length.not.valid=长度必须在{min}到{max}个字符之间
+
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.password.not.valid=* 5-50个字符
+ 
+user.email.not.valid=邮箱格式错误
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml
new file mode 100644
index 0000000..a360583
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/logback.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/ruoyi/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+	
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+	
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+	
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+	
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.ruoyi" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+	
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+	
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration> 
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mapper/system/TrackerProjectMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/TrackerProjectMapper.xml
new file mode 100644
index 0000000..21e2223
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/TrackerProjectMapper.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.tracker.mapper.TrackerProjectMapper">
+    
+    <resultMap type="TrackerProject" id="TrackerProjectResult">
+        <result property="projectId"    column="project_id"    />
+        <result property="projectName"    column="project_name"    />
+        <result property="description"    column="description"    />
+        <result property="status"    column="status"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectTrackerProjectVo">
+        select project_id, project_name, description, status, create_by, create_time, update_by, update_time from tracker_project
+    </sql>
+
+    <select id="selectTrackerProjectList" parameterType="TrackerProject" resultMap="TrackerProjectResult">
+        <include refid="selectTrackerProjectVo"/>
+        <where>  
+            <if test="projectName != null  and projectName != ''"> and project_name like concat('%', #{projectName}, '%')</if>
+            <if test="description != null  and description != ''"> and description = #{description}</if>
+            <if test="status != null  and status != ''"> and status = #{status}</if>
+        </where>
+    </select>
+    
+    <select id="selectTrackerProjectByProjectId" parameterType="Long" resultMap="TrackerProjectResult">
+        <include refid="selectTrackerProjectVo"/>
+        where project_id = #{projectId}
+    </select>
+
+    <insert id="insertTrackerProject" parameterType="TrackerProject" useGeneratedKeys="true" keyProperty="projectId">
+        insert into tracker_project
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="projectName != null and projectName != ''">project_name,</if>
+            <if test="description != null">description,</if>
+            <if test="status != null">status,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="projectName != null and projectName != ''">#{projectName},</if>
+            <if test="description != null">#{description},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateTrackerProject" parameterType="TrackerProject">
+        update tracker_project
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="projectName != null and projectName != ''">project_name = #{projectName},</if>
+            <if test="description != null">description = #{description},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where project_id = #{projectId}
+    </update>
+
+    <delete id="deleteTrackerProjectByProjectId" parameterType="Long">
+        delete from tracker_project where project_id = #{projectId}
+    </delete>
+
+    <delete id="deleteTrackerProjectByProjectIds" parameterType="String">
+        delete from tracker_project where project_id in 
+        <foreach item="projectId" collection="array" open="(" separator="," close=")">
+            #{projectId}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mapper/system/TrackerProjectUserMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/TrackerProjectUserMapper.xml
new file mode 100644
index 0000000..aa543d6
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/TrackerProjectUserMapper.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.tracker.mapper.TrackerProjectUserMapper">
+    
+    <resultMap type="TrackerProjectUser" id="TrackerProjectUserResult">
+        <result property="projectId"    column="project_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="role"    column="role"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectTrackerProjectUserVo">
+        select project_id, user_id, role, create_time from tracker_project_user
+    </sql>
+
+    <select id="selectTrackerProjectUserList" parameterType="TrackerProjectUser" resultMap="TrackerProjectUserResult">
+        <include refid="selectTrackerProjectUserVo"/>
+        <where>  
+            <if test="role != null  and role != ''"> and role = #{role}</if>
+        </where>
+    </select>
+    
+    <select id="selectTrackerProjectUserByProjectId" parameterType="Long" resultMap="TrackerProjectUserResult">
+        <include refid="selectTrackerProjectUserVo"/>
+        where project_id = #{projectId}
+    </select>
+
+    <insert id="insertTrackerProjectUser" parameterType="TrackerProjectUser">
+        insert into tracker_project_user
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="projectId != null">project_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="role != null and role != ''">role,</if>
+            <if test="createTime != null">create_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="projectId != null">#{projectId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="role != null and role != ''">#{role},</if>
+            <if test="createTime != null">#{createTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateTrackerProjectUser" parameterType="TrackerProjectUser">
+        update tracker_project_user
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="role != null and role != ''">role = #{role},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where project_id = #{projectId}
+    </update>
+
+    <delete id="deleteTrackerProjectUserByProjectId" parameterType="Long">
+        delete from tracker_project_user where project_id = #{projectId}
+    </delete>
+
+    <delete id="deleteTrackerProjectUserByProjectIds" parameterType="String">
+        delete from tracker_project_user where project_id in 
+        <foreach item="projectId" collection="array" open="(" separator="," close=")">
+            #{projectId}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mapper/system/TrackerTaskLogMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/TrackerTaskLogMapper.xml
new file mode 100644
index 0000000..117d057
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/TrackerTaskLogMapper.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.tracker.mapper.TrackerTaskLogMapper">
+    
+    <resultMap type="TrackerTaskLog" id="TrackerTaskLogResult">
+        <result property="logId"    column="log_id"    />
+        <result property="taskId"    column="task_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="action"    column="action"    />
+        <result property="description"    column="description"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectTrackerTaskLogVo">
+        select log_id, task_id, user_id, action, description, create_time from tracker_task_log
+    </sql>
+
+    <select id="selectTrackerTaskLogList" parameterType="TrackerTaskLog" resultMap="TrackerTaskLogResult">
+        <include refid="selectTrackerTaskLogVo"/>
+        <where>  
+            <if test="taskId != null "> and task_id = #{taskId}</if>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="action != null  and action != ''"> and action = #{action}</if>
+            <if test="description != null  and description != ''"> and description = #{description}</if>
+        </where>
+    </select>
+    
+    <select id="selectTrackerTaskLogByLogId" parameterType="Long" resultMap="TrackerTaskLogResult">
+        <include refid="selectTrackerTaskLogVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <insert id="insertTrackerTaskLog" parameterType="TrackerTaskLog" useGeneratedKeys="true" keyProperty="logId">
+        insert into tracker_task_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="taskId != null">task_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="action != null and action != ''">action,</if>
+            <if test="description != null">description,</if>
+            <if test="createTime != null">create_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="taskId != null">#{taskId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="action != null and action != ''">#{action},</if>
+            <if test="description != null">#{description},</if>
+            <if test="createTime != null">#{createTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateTrackerTaskLog" parameterType="TrackerTaskLog">
+        update tracker_task_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="taskId != null">task_id = #{taskId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="action != null and action != ''">action = #{action},</if>
+            <if test="description != null">description = #{description},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <delete id="deleteTrackerTaskLogByLogId" parameterType="Long">
+        delete from tracker_task_log where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteTrackerTaskLogByLogIds" parameterType="String">
+        delete from tracker_task_log where log_id in 
+        <foreach item="logId" collection="array" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mapper/system/TrackerTaskMapper.xml b/ruoyi-admin/src/main/resources/mapper/system/TrackerTaskMapper.xml
new file mode 100644
index 0000000..0042ed0
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mapper/system/TrackerTaskMapper.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.tracker.mapper.TrackerTaskMapper">
+    
+    <resultMap type="TrackerTask" id="TrackerTaskResult">
+        <result property="taskId"    column="task_id"    />
+        <result property="projectId"    column="project_id"    />
+        <result property="taskName"    column="task_name"    />
+        <result property="description"    column="description"    />
+        <result property="assignedTo"    column="assigned_to"    />
+        <result property="status"    column="status"    />
+        <result property="priority"    column="priority"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectTrackerTaskVo">
+        select task_id, project_id, task_name, description, assigned_to, status, priority, create_time, update_time from tracker_task
+    </sql>
+
+    <select id="selectTrackerTaskList" parameterType="TrackerTask" resultMap="TrackerTaskResult">
+        <include refid="selectTrackerTaskVo"/>
+        <where>  
+            <if test="projectId != null "> and project_id = #{projectId}</if>
+            <if test="taskName != null  and taskName != ''"> and task_name like concat('%', #{taskName}, '%')</if>
+            <if test="description != null  and description != ''"> and description = #{description}</if>
+            <if test="assignedTo != null "> and assigned_to = #{assignedTo}</if>
+            <if test="status != null  and status != ''"> and status = #{status}</if>
+            <if test="priority != null  and priority != ''"> and priority = #{priority}</if>
+        </where>
+    </select>
+    
+    <select id="selectTrackerTaskByTaskId" parameterType="Long" resultMap="TrackerTaskResult">
+        <include refid="selectTrackerTaskVo"/>
+        where task_id = #{taskId}
+    </select>
+
+    <insert id="insertTrackerTask" parameterType="TrackerTask" useGeneratedKeys="true" keyProperty="taskId">
+        insert into tracker_task
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="projectId != null">project_id,</if>
+            <if test="taskName != null and taskName != ''">task_name,</if>
+            <if test="description != null">description,</if>
+            <if test="assignedTo != null">assigned_to,</if>
+            <if test="status != null">status,</if>
+            <if test="priority != null">priority,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="projectId != null">#{projectId},</if>
+            <if test="taskName != null and taskName != ''">#{taskName},</if>
+            <if test="description != null">#{description},</if>
+            <if test="assignedTo != null">#{assignedTo},</if>
+            <if test="status != null">#{status},</if>
+            <if test="priority != null">#{priority},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateTrackerTask" parameterType="TrackerTask">
+        update tracker_task
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="projectId != null">project_id = #{projectId},</if>
+            <if test="taskName != null and taskName != ''">task_name = #{taskName},</if>
+            <if test="description != null">description = #{description},</if>
+            <if test="assignedTo != null">assigned_to = #{assignedTo},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="priority != null">priority = #{priority},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where task_id = #{taskId}
+    </update>
+
+    <delete id="deleteTrackerTaskByTaskId" parameterType="Long">
+        delete from tracker_task where task_id = #{taskId}
+    </delete>
+
+    <delete id="deleteTrackerTaskByTaskIds" parameterType="String">
+        delete from tracker_task where task_id in 
+        <foreach item="taskId" collection="array" open="(" separator="," close=")">
+            #{taskId}
+        </foreach>
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml b/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml
new file mode 100644
index 0000000..ac47c03
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration
+PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-config.dtd">
+<configuration>
+    <!-- 全局参数 -->
+    <settings>
+        <!-- 使全局的映射器启用或禁用缓存 -->
+        <setting name="cacheEnabled"             value="true"   />
+        <!-- 允许JDBC 支持自动生成主键 -->
+        <setting name="useGeneratedKeys"         value="true"   />
+        <!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 -->
+        <setting name="defaultExecutorType"      value="SIMPLE" />
+		<!-- 指定 MyBatis 所用日志的具体实现 -->
+        <setting name="logImpl"                  value="SLF4J"  />
+        <!-- 使用驼峰命名法转换字段 -->
+		<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> -->
+	</settings>
+	
+</configuration>
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
new file mode 100644
index 0000000..e6a6687
--- /dev/null
+++ b/ruoyi-common/pom.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.8.8</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common</artifactId>
+
+    <description>
+        common通用工具
+    </description>
+
+    <dependencies>
+
+        <!-- Spring框架基本的核心工具 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+        </dependency>
+
+        <!-- SpringWeb模块 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+
+        <!-- spring security 安全认证 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- pagehelper 分页插件 -->
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 自定义验证注解 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!--常用工具类 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+  
+        <!-- JSON工具类 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        
+        <!-- 阿里JSON解析器 -->
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+        </dependency>
+
+        <!-- io常用工具类 -->
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+
+        <!-- excel工具 -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+        </dependency>
+
+        <!-- yml解析器 -->
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+        </dependency>
+
+        <!-- Token生成与解析-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+        </dependency>
+
+        <!-- Jaxb -->
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+        </dependency>
+
+        <!-- redis 缓存操作 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <!-- pool 对象池 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <!-- 解析客户端操作系统、浏览器等 -->
+        <dependency>
+            <groupId>eu.bitwalker</groupId>
+            <artifactId>UserAgentUtils</artifactId>
+        </dependency>
+
+        <!-- servlet包 -->
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java
new file mode 100644
index 0000000..1d6d4f4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java
@@ -0,0 +1,19 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 匿名访问不鉴权注解
+ * 
+ * @author ruoyi
+ */
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Anonymous
+{
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java
new file mode 100644
index 0000000..be49c80
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java
@@ -0,0 +1,33 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 数据权限过滤注解
+ * 
+ * @author ruoyi
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataScope
+{
+    /**
+     * 部门表的别名
+     */
+    public String deptAlias() default "";
+
+    /**
+     * 用户表的别名
+     */
+    public String userAlias() default "";
+
+    /**
+     * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来
+     */
+    public String permission() default "";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java
new file mode 100644
index 0000000..79cd191
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java
@@ -0,0 +1,28 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import com.ruoyi.common.enums.DataSourceType;
+
+/**
+ * 自定义多数据源切换注解
+ *
+ * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
+ *
+ * @author ruoyi
+ */
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface DataSource
+{
+    /**
+     * 切换数据源名称
+     */
+    public DataSourceType value() default DataSourceType.MASTER;
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java
new file mode 100644
index 0000000..27e587d
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java
@@ -0,0 +1,192 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import com.ruoyi.common.utils.poi.ExcelHandlerAdapter;
+
+/**
+ * 自定义导出Excel数据注解
+ * 
+ * @author ruoyi
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Excel
+{
+    /**
+     * 导出时在excel中排序
+     */
+    public int sort() default Integer.MAX_VALUE;
+
+    /**
+     * 导出到Excel中的名字.
+     */
+    public String name() default "";
+
+    /**
+     * 日期格式, 如: yyyy-MM-dd
+     */
+    public String dateFormat() default "";
+
+    /**
+     * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
+     */
+    public String dictType() default "";
+
+    /**
+     * 读取内容转表达式 (如: 0=男,1=女,2=未知)
+     */
+    public String readConverterExp() default "";
+
+    /**
+     * 分隔符,读取字符串组内容
+     */
+    public String separator() default ",";
+
+    /**
+     * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
+     */
+    public int scale() default -1;
+
+    /**
+     * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
+     */
+    public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
+
+    /**
+     * 导出时在excel中每个列的高度
+     */
+    public double height() default 14;
+
+    /**
+     * 导出时在excel中每个列的宽度
+     */
+    public double width() default 16;
+
+    /**
+     * 文字后缀,如% 90 变成90%
+     */
+    public String suffix() default "";
+
+    /**
+     * 当值为空时,字段的默认值
+     */
+    public String defaultValue() default "";
+
+    /**
+     * 提示信息
+     */
+    public String prompt() default "";
+
+    /**
+     * 设置只能选择不能输入的列内容.
+     */
+    public String[] combo() default {};
+
+    /**
+     * 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解.
+     */
+    public boolean comboReadDict() default false;
+
+    /**
+     * 是否需要纵向合并单元格,应对需求:含有list集合单元格)
+     */
+    public boolean needMerge() default false;
+
+    /**
+     * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
+     */
+    public boolean isExport() default true;
+
+    /**
+     * 另一个类中的属性名称,支持多级获取,以小数点隔开
+     */
+    public String targetAttr() default "";
+
+    /**
+     * 是否自动统计数据,在最后追加一行统计数据总和
+     */
+    public boolean isStatistics() default false;
+
+    /**
+     * 导出类型(0数字 1字符串 2图片)
+     */
+    public ColumnType cellType() default ColumnType.STRING;
+
+    /**
+     * 导出列头背景颜色
+     */
+    public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;
+
+    /**
+     * 导出列头字体颜色
+     */
+    public IndexedColors headerColor() default IndexedColors.WHITE;
+
+    /**
+     * 导出单元格背景颜色
+     */
+    public IndexedColors backgroundColor() default IndexedColors.WHITE;
+
+    /**
+     * 导出单元格字体颜色
+     */
+    public IndexedColors color() default IndexedColors.BLACK;
+
+    /**
+     * 导出字段对齐方式
+     */
+    public HorizontalAlignment align() default HorizontalAlignment.CENTER;
+
+    /**
+     * 自定义数据处理器
+     */
+    public Class<?> handler() default ExcelHandlerAdapter.class;
+
+    /**
+     * 自定义数据处理器参数
+     */
+    public String[] args() default {};
+
+    /**
+     * 字段类型(0:导出导入;1:仅导出;2:仅导入)
+     */
+    Type type() default Type.ALL;
+
+    public enum Type
+    {
+        ALL(0), EXPORT(1), IMPORT(2);
+        private final int value;
+
+        Type(int value)
+        {
+            this.value = value;
+        }
+
+        public int value()
+        {
+            return this.value;
+        }
+    }
+
+    public enum ColumnType
+    {
+        NUMERIC(0), STRING(1), IMAGE(2), TEXT(3);
+        private final int value;
+
+        ColumnType(int value)
+        {
+            this.value = value;
+        }
+
+        public int value()
+        {
+            return this.value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java
new file mode 100644
index 0000000..1f1cc81
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java
@@ -0,0 +1,18 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Excel注解集
+ * 
+ * @author ruoyi
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Excels
+{
+    public Excel[] value();
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
new file mode 100644
index 0000000..1eb8e49
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java
@@ -0,0 +1,51 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.enums.OperatorType;
+
+/**
+ * 自定义操作日志记录注解
+ * 
+ * @author ruoyi
+ *
+ */
+@Target({ ElementType.PARAMETER, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log
+{
+    /**
+     * 模块
+     */
+    public String title() default "";
+
+    /**
+     * 功能
+     */
+    public BusinessType businessType() default BusinessType.OTHER;
+
+    /**
+     * 操作人类别
+     */
+    public OperatorType operatorType() default OperatorType.MANAGE;
+
+    /**
+     * 是否保存请求的参数
+     */
+    public boolean isSaveRequestData() default true;
+
+    /**
+     * 是否保存响应的参数
+     */
+    public boolean isSaveResponseData() default true;
+
+    /**
+     * 排除指定的请求参数
+     */
+    public String[] excludeParamNames() default {};
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
new file mode 100644
index 0000000..0f024c7
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
@@ -0,0 +1,40 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.enums.LimitType;
+
+/**
+ * 限流注解
+ * 
+ * @author ruoyi
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RateLimiter
+{
+    /**
+     * 限流key
+     */
+    public String key() default CacheConstants.RATE_LIMIT_KEY;
+
+    /**
+     * 限流时间,单位秒
+     */
+    public int time() default 60;
+
+    /**
+     * 限流次数
+     */
+    public int count() default 100;
+
+    /**
+     * 限流类型
+     */
+    public LimitType limitType() default LimitType.DEFAULT;
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
new file mode 100644
index 0000000..b769748
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
@@ -0,0 +1,31 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 自定义注解防止表单重复提交
+ * 
+ * @author ruoyi
+ *
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit
+{
+    /**
+     * 间隔时间(ms),小于此时间视为重复提交
+     */
+    public int interval() default 5000;
+
+    /**
+     * 提示消息
+     */
+    public String message() default "不允许重复提交,请稍候再试";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java
new file mode 100644
index 0000000..c0621e9
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.ruoyi.common.config.serializer.SensitiveJsonSerializer;
+import com.ruoyi.common.enums.DesensitizedType;
+
+/**
+ * 数据脱敏注解
+ *
+ * @author ruoyi
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@JacksonAnnotationsInside
+@JsonSerialize(using = SensitiveJsonSerializer.class)
+public @interface Sensitive
+{
+    DesensitizedType desensitizedType();
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java
new file mode 100644
index 0000000..29281cf
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java
@@ -0,0 +1,122 @@
+package com.ruoyi.common.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取项目相关配置
+ * 
+ * @author ruoyi
+ */
+@Component
+@ConfigurationProperties(prefix = "ruoyi")
+public class RuoYiConfig
+{
+    /** 项目名称 */
+    private String name;
+
+    /** 版本 */
+    private String version;
+
+    /** 版权年份 */
+    private String copyrightYear;
+
+    /** 上传路径 */
+    private static String profile;
+
+    /** 获取地址开关 */
+    private static boolean addressEnabled;
+
+    /** 验证码类型 */
+    private static String captchaType;
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    public String getVersion()
+    {
+        return version;
+    }
+
+    public void setVersion(String version)
+    {
+        this.version = version;
+    }
+
+    public String getCopyrightYear()
+    {
+        return copyrightYear;
+    }
+
+    public void setCopyrightYear(String copyrightYear)
+    {
+        this.copyrightYear = copyrightYear;
+    }
+
+    public static String getProfile()
+    {
+        return profile;
+    }
+
+    public void setProfile(String profile)
+    {
+        RuoYiConfig.profile = profile;
+    }
+
+    public static boolean isAddressEnabled()
+    {
+        return addressEnabled;
+    }
+
+    public void setAddressEnabled(boolean addressEnabled)
+    {
+        RuoYiConfig.addressEnabled = addressEnabled;
+    }
+
+    public static String getCaptchaType() {
+        return captchaType;
+    }
+
+    public void setCaptchaType(String captchaType) {
+        RuoYiConfig.captchaType = captchaType;
+    }
+
+    /**
+     * 获取导入上传路径
+     */
+    public static String getImportPath()
+    {
+        return getProfile() + "/import";
+    }
+
+    /**
+     * 获取头像上传路径
+     */
+    public static String getAvatarPath()
+    {
+        return getProfile() + "/avatar";
+    }
+
+    /**
+     * 获取下载路径
+     */
+    public static String getDownloadPath()
+    {
+        return getProfile() + "/download/";
+    }
+
+    /**
+     * 获取上传路径
+     */
+    public static String getUploadPath()
+    {
+        return getProfile() + "/upload";
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java
new file mode 100644
index 0000000..e819a1d
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java
@@ -0,0 +1,67 @@
+package com.ruoyi.common.config.serializer;
+
+import java.io.IOException;
+import java.util.Objects;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.ruoyi.common.annotation.Sensitive;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.enums.DesensitizedType;
+import com.ruoyi.common.utils.SecurityUtils;
+
+/**
+ * 数据脱敏序列化过滤
+ *
+ * @author ruoyi
+ */
+public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer
+{
+    private DesensitizedType desensitizedType;
+
+    @Override
+    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException
+    {
+        if (desensitization())
+        {
+            gen.writeString(desensitizedType.desensitizer().apply(value));
+        }
+        else
+        {
+            gen.writeString(value);
+        }
+    }
+
+    @Override
+    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
+            throws JsonMappingException
+    {
+        Sensitive annotation = property.getAnnotation(Sensitive.class);
+        if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
+        {
+            this.desensitizedType = annotation.desensitizedType();
+            return this;
+        }
+        return prov.findValueSerializer(property.getType(), property);
+    }
+
+    /**
+     * 是否需要脱敏处理
+     */
+    private boolean desensitization()
+    {
+        try
+        {
+            LoginUser securityUser = SecurityUtils.getLoginUser();
+            // 管理员不脱敏
+            return !securityUser.getUser().isAdmin();
+        }
+        catch (Exception e)
+        {
+            return true;
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
new file mode 100644
index 0000000..0080343
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
@@ -0,0 +1,44 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 缓存的key 常量
+ * 
+ * @author ruoyi
+ */
+public class CacheConstants
+{
+    /**
+     * 登录用户 redis key
+     */
+    public static final String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    /**
+     * 验证码 redis key
+     */
+    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 参数管理 cache key
+     */
+    public static final String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 防重提交 redis key
+     */
+    public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+    /**
+     * 限流 redis key
+     */
+    public static final String RATE_LIMIT_KEY = "rate_limit:";
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
new file mode 100644
index 0000000..0c384c6
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
@@ -0,0 +1,173 @@
+package com.ruoyi.common.constant;
+
+import java.util.Locale;
+import io.jsonwebtoken.Claims;
+
+/**
+ * 通用常量信息
+ * 
+ * @author ruoyi
+ */
+public class Constants
+{
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * 系统语言
+     */
+    public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
+
+    /**
+     * www主域
+     */
+    public static final String WWW = "www.";
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    public static final String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    public static final String LOGOUT = "Logout";
+
+    /**
+     * 注册
+     */
+    public static final String REGISTER = "Register";
+
+    /**
+     * 登录失败
+     */
+    public static final String LOGIN_FAIL = "Error";
+
+    /**
+     * 所有权限标识
+     */
+    public static final String ALL_PERMISSION = "*:*:*";
+
+    /**
+     * 管理员角色权限标识
+     */
+    public static final String SUPER_ADMIN = "admin";
+
+    /**
+     * 角色权限分隔符
+     */
+    public static final String ROLE_DELIMETER = ",";
+
+    /**
+     * 权限标识分隔符
+     */
+    public static final String PERMISSION_DELIMETER = ",";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    /**
+     * 用户ID
+     */
+    public static final String JWT_USERID = "userid";
+
+    /**
+     * 用户名称
+     */
+    public static final String JWT_USERNAME = Claims.SUBJECT;
+
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+
+    /**
+     * RMI 远程方法调用
+     */
+    public static final String LOOKUP_RMI = "rmi:";
+
+    /**
+     * LDAP 远程方法调用
+     */
+    public static final String LOOKUP_LDAP = "ldap:";
+
+    /**
+     * LDAPS 远程方法调用
+     */
+    public static final String LOOKUP_LDAPS = "ldaps:";
+
+    /**
+     * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
+     */
+    public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
+
+    /**
+     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
+     */
+    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };
+
+    /**
+     * 定时任务违规的字符
+     */
+    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
new file mode 100644
index 0000000..7d899d4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
@@ -0,0 +1,117 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 代码生成通用常量
+ * 
+ * @author ruoyi
+ */
+public class GenConstants
+{
+    /** 单表(增删改查) */
+    public static final String TPL_CRUD = "crud";
+
+    /** 树表(增删改查) */
+    public static final String TPL_TREE = "tree";
+
+    /** 主子表(增删改查) */
+    public static final String TPL_SUB = "sub";
+
+    /** 树编码字段 */
+    public static final String TREE_CODE = "treeCode";
+
+    /** 树父编码字段 */
+    public static final String TREE_PARENT_CODE = "treeParentCode";
+
+    /** 树名称字段 */
+    public static final String TREE_NAME = "treeName";
+
+    /** 上级菜单ID字段 */
+    public static final String PARENT_MENU_ID = "parentMenuId";
+
+    /** 上级菜单名称字段 */
+    public static final String PARENT_MENU_NAME = "parentMenuName";
+
+    /** 数据库字符串类型 */
+    public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };
+
+    /** 数据库文本类型 */
+    public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" };
+
+    /** 数据库时间类型 */
+    public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" };
+
+    /** 数据库数字类型 */
+    public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
+            "bit", "bigint", "float", "double", "decimal" };
+
+    /** 页面不需要编辑字段 */
+    public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" };
+
+    /** 页面不需要显示的列表字段 */
+    public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by",
+            "update_time" };
+
+    /** 页面不需要查询字段 */
+    public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by",
+            "update_time", "remark" };
+
+    /** Entity基类字段 */
+    public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" };
+
+    /** Tree基类字段 */
+    public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" };
+
+    /** 文本框 */
+    public static final String HTML_INPUT = "input";
+
+    /** 文本域 */
+    public static final String HTML_TEXTAREA = "textarea";
+
+    /** 下拉框 */
+    public static final String HTML_SELECT = "select";
+
+    /** 单选框 */
+    public static final String HTML_RADIO = "radio";
+
+    /** 复选框 */
+    public static final String HTML_CHECKBOX = "checkbox";
+
+    /** 日期控件 */
+    public static final String HTML_DATETIME = "datetime";
+
+    /** 图片上传控件 */
+    public static final String HTML_IMAGE_UPLOAD = "imageUpload";
+
+    /** 文件上传控件 */
+    public static final String HTML_FILE_UPLOAD = "fileUpload";
+
+    /** 富文本控件 */
+    public static final String HTML_EDITOR = "editor";
+
+    /** 字符串类型 */
+    public static final String TYPE_STRING = "String";
+
+    /** 整型 */
+    public static final String TYPE_INTEGER = "Integer";
+
+    /** 长整型 */
+    public static final String TYPE_LONG = "Long";
+
+    /** 浮点型 */
+    public static final String TYPE_DOUBLE = "Double";
+
+    /** 高精度计算类型 */
+    public static final String TYPE_BIGDECIMAL = "BigDecimal";
+
+    /** 时间类型 */
+    public static final String TYPE_DATE = "Date";
+
+    /** 模糊查询 */
+    public static final String QUERY_LIKE = "LIKE";
+
+    /** 相等查询 */
+    public static final String QUERY_EQ = "EQ";
+
+    /** 需要 */
+    public static final String REQUIRE = "1";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java
new file mode 100644
index 0000000..a983c77
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java
@@ -0,0 +1,94 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 返回状态码
+ * 
+ * @author ruoyi
+ */
+public class HttpStatus
+{
+    /**
+     * 操作成功
+     */
+    public static final int SUCCESS = 200;
+
+    /**
+     * 对象创建成功
+     */
+    public static final int CREATED = 201;
+
+    /**
+     * 请求已经被接受
+     */
+    public static final int ACCEPTED = 202;
+
+    /**
+     * 操作已经执行成功,但是没有返回数据
+     */
+    public static final int NO_CONTENT = 204;
+
+    /**
+     * 资源已被移除
+     */
+    public static final int MOVED_PERM = 301;
+
+    /**
+     * 重定向
+     */
+    public static final int SEE_OTHER = 303;
+
+    /**
+     * 资源没有被修改
+     */
+    public static final int NOT_MODIFIED = 304;
+
+    /**
+     * 参数列表错误(缺少,格式不匹配)
+     */
+    public static final int BAD_REQUEST = 400;
+
+    /**
+     * 未授权
+     */
+    public static final int UNAUTHORIZED = 401;
+
+    /**
+     * 访问受限,授权过期
+     */
+    public static final int FORBIDDEN = 403;
+
+    /**
+     * 资源,服务未找到
+     */
+    public static final int NOT_FOUND = 404;
+
+    /**
+     * 不允许的http方法
+     */
+    public static final int BAD_METHOD = 405;
+
+    /**
+     * 资源冲突,或者资源被锁
+     */
+    public static final int CONFLICT = 409;
+
+    /**
+     * 不支持的数据,媒体类型
+     */
+    public static final int UNSUPPORTED_TYPE = 415;
+
+    /**
+     * 系统内部错误
+     */
+    public static final int ERROR = 500;
+
+    /**
+     * 接口未实现
+     */
+    public static final int NOT_IMPLEMENTED = 501;
+
+    /**
+     * 系统警告消息
+     */
+    public static final int WARN = 601;
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java
new file mode 100644
index 0000000..62ad815
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java
@@ -0,0 +1,50 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 任务调度通用常量
+ * 
+ * @author ruoyi
+ */
+public class ScheduleConstants
+{
+    public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";
+
+    /** 执行目标key */
+    public static final String TASK_PROPERTIES = "TASK_PROPERTIES";
+
+    /** 默认 */
+    public static final String MISFIRE_DEFAULT = "0";
+
+    /** 立即触发执行 */
+    public static final String MISFIRE_IGNORE_MISFIRES = "1";
+
+    /** 触发一次执行 */
+    public static final String MISFIRE_FIRE_AND_PROCEED = "2";
+
+    /** 不触发立即执行 */
+    public static final String MISFIRE_DO_NOTHING = "3";
+
+    public enum Status
+    {
+        /**
+         * 正常
+         */
+        NORMAL("0"),
+        /**
+         * 暂停
+         */
+        PAUSE("1");
+
+        private String value;
+
+        private Status(String value)
+        {
+            this.value = value;
+        }
+
+        public String getValue()
+        {
+            return value;
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
new file mode 100644
index 0000000..8dc7faa
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
@@ -0,0 +1,81 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 用户常量信息
+ * 
+ * @author ruoyi
+ */
+public class UserConstants
+{
+    /**
+     * 平台内系统用户的唯一标志
+     */
+    public static final String SYS_USER = "SYS_USER";
+
+    /** 正常状态 */
+    public static final String NORMAL = "0";
+
+    /** 异常状态 */
+    public static final String EXCEPTION = "1";
+
+    /** 用户封禁状态 */
+    public static final String USER_DISABLE = "1";
+
+    /** 角色正常状态 */
+    public static final String ROLE_NORMAL = "0";
+
+    /** 角色封禁状态 */
+    public static final String ROLE_DISABLE = "1";
+
+    /** 部门正常状态 */
+    public static final String DEPT_NORMAL = "0";
+
+    /** 部门停用状态 */
+    public static final String DEPT_DISABLE = "1";
+
+    /** 字典正常状态 */
+    public static final String DICT_NORMAL = "0";
+
+    /** 是否为系统默认(是) */
+    public static final String YES = "Y";
+
+    /** 是否菜单外链(是) */
+    public static final String YES_FRAME = "0";
+
+    /** 是否菜单外链(否) */
+    public static final String NO_FRAME = "1";
+
+    /** 菜单类型(目录) */
+    public static final String TYPE_DIR = "M";
+
+    /** 菜单类型(菜单) */
+    public static final String TYPE_MENU = "C";
+
+    /** 菜单类型(按钮) */
+    public static final String TYPE_BUTTON = "F";
+
+    /** Layout组件标识 */
+    public final static String LAYOUT = "Layout";
+    
+    /** ParentView组件标识 */
+    public final static String PARENT_VIEW = "ParentView";
+
+    /** InnerLink组件标识 */
+    public final static String INNER_LINK = "InnerLink";
+
+    /** 校验是否唯一的返回标识 */
+    public final static boolean UNIQUE = true;
+    public final static boolean NOT_UNIQUE = false;
+
+    /**
+     * 用户名长度限制
+     */
+    public static final int USERNAME_MIN_LENGTH = 2;
+    public static final int USERNAME_MAX_LENGTH = 20;
+
+    /**
+     * 密码长度限制
+     */
+    public static final int PASSWORD_MIN_LENGTH = 5;
+    public static final int PASSWORD_MAX_LENGTH = 20;
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java
new file mode 100644
index 0000000..a685e06
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java
@@ -0,0 +1,202 @@
+package com.ruoyi.common.core.controller;
+
+import java.beans.PropertyEditorSupport;
+import java.util.Date;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.page.PageDomain;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.page.TableSupport;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.PageUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.sql.SqlUtil;
+
+/**
+ * web层通用数据处理
+ * 
+ * @author ruoyi
+ */
+public class BaseController
+{
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * 将前台传递过来的日期格式的字符串,自动转化为Date类型
+     */
+    @InitBinder
+    public void initBinder(WebDataBinder binder)
+    {
+        // Date 类型转换
+        binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
+        {
+            @Override
+            public void setAsText(String text)
+            {
+                setValue(DateUtils.parseDate(text));
+            }
+        });
+    }
+
+    /**
+     * 设置请求分页数据
+     */
+    protected void startPage()
+    {
+        PageUtils.startPage();
+    }
+
+    /**
+     * 设置请求排序数据
+     */
+    protected void startOrderBy()
+    {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        if (StringUtils.isNotEmpty(pageDomain.getOrderBy()))
+        {
+            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
+            PageHelper.orderBy(orderBy);
+        }
+    }
+
+    /**
+     * 清理分页的线程变量
+     */
+    protected void clearPage()
+    {
+        PageUtils.clearPage();
+    }
+
+    /**
+     * 响应请求分页数据
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected TableDataInfo getDataTable(List<?> list)
+    {
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(new PageInfo(list).getTotal());
+        return rspData;
+    }
+
+    /**
+     * 返回成功
+     */
+    public AjaxResult success()
+    {
+        return AjaxResult.success();
+    }
+
+    /**
+     * 返回失败消息
+     */
+    public AjaxResult error()
+    {
+        return AjaxResult.error();
+    }
+
+    /**
+     * 返回成功消息
+     */
+    public AjaxResult success(String message)
+    {
+        return AjaxResult.success(message);
+    }
+    
+    /**
+     * 返回成功消息
+     */
+    public AjaxResult success(Object data)
+    {
+        return AjaxResult.success(data);
+    }
+
+    /**
+     * 返回失败消息
+     */
+    public AjaxResult error(String message)
+    {
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 返回警告消息
+     */
+    public AjaxResult warn(String message)
+    {
+        return AjaxResult.warn(message);
+    }
+
+    /**
+     * 响应返回结果
+     * 
+     * @param rows 影响行数
+     * @return 操作结果
+     */
+    protected AjaxResult toAjax(int rows)
+    {
+        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
+    }
+
+    /**
+     * 响应返回结果
+     * 
+     * @param result 结果
+     * @return 操作结果
+     */
+    protected AjaxResult toAjax(boolean result)
+    {
+        return result ? success() : error();
+    }
+
+    /**
+     * 页面跳转
+     */
+    public String redirect(String url)
+    {
+        return StringUtils.format("redirect:{}", url);
+    }
+
+    /**
+     * 获取用户缓存信息
+     */
+    public LoginUser getLoginUser()
+    {
+        return SecurityUtils.getLoginUser();
+    }
+
+    /**
+     * 获取登录用户id
+     */
+    public Long getUserId()
+    {
+        return getLoginUser().getUserId();
+    }
+
+    /**
+     * 获取登录部门id
+     */
+    public Long getDeptId()
+    {
+        return getLoginUser().getDeptId();
+    }
+
+    /**
+     * 获取登录用户名
+     */
+    public String getUsername()
+    {
+        return getLoginUser().getUsername();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java
new file mode 100644
index 0000000..a7abfe4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java
@@ -0,0 +1,216 @@
+package com.ruoyi.common.core.domain;
+
+import java.util.HashMap;
+import java.util.Objects;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 操作消息提醒
+ * 
+ * @author ruoyi
+ */
+public class AjaxResult extends HashMap<String, Object>
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 状态码 */
+    public static final String CODE_TAG = "code";
+
+    /** 返回内容 */
+    public static final String MSG_TAG = "msg";
+
+    /** 数据对象 */
+    public static final String DATA_TAG = "data";
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
+     */
+    public AjaxResult()
+    {
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     * 
+     * @param code 状态码
+     * @param msg 返回内容
+     */
+    public AjaxResult(int code, String msg)
+    {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     * 
+     * @param code 状态码
+     * @param msg 返回内容
+     * @param data 数据对象
+     */
+    public AjaxResult(int code, String msg, Object data)
+    {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+        if (StringUtils.isNotNull(data))
+        {
+            super.put(DATA_TAG, data);
+        }
+    }
+
+    /**
+     * 返回成功消息
+     * 
+     * @return 成功消息
+     */
+    public static AjaxResult success()
+    {
+        return AjaxResult.success("操作成功");
+    }
+
+    /**
+     * 返回成功数据
+     * 
+     * @return 成功消息
+     */
+    public static AjaxResult success(Object data)
+    {
+        return AjaxResult.success("操作成功", data);
+    }
+
+    /**
+     * 返回成功消息
+     * 
+     * @param msg 返回内容
+     * @return 成功消息
+     */
+    public static AjaxResult success(String msg)
+    {
+        return AjaxResult.success(msg, null);
+    }
+
+    /**
+     * 返回成功消息
+     * 
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 成功消息
+     */
+    public static AjaxResult success(String msg, Object data)
+    {
+        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static AjaxResult warn(String msg)
+    {
+        return AjaxResult.warn(msg, null);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static AjaxResult warn(String msg, Object data)
+    {
+        return new AjaxResult(HttpStatus.WARN, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     * 
+     * @return 错误消息
+     */
+    public static AjaxResult error()
+    {
+        return AjaxResult.error("操作失败");
+    }
+
+    /**
+     * 返回错误消息
+     * 
+     * @param msg 返回内容
+     * @return 错误消息
+     */
+    public static AjaxResult error(String msg)
+    {
+        return AjaxResult.error(msg, null);
+    }
+
+    /**
+     * 返回错误消息
+     * 
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 错误消息
+     */
+    public static AjaxResult error(String msg, Object data)
+    {
+        return new AjaxResult(HttpStatus.ERROR, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     * 
+     * @param code 状态码
+     * @param msg 返回内容
+     * @return 错误消息
+     */
+    public static AjaxResult error(int code, String msg)
+    {
+        return new AjaxResult(code, msg, null);
+    }
+
+    /**
+     * 是否为成功消息
+     *
+     * @return 结果
+     */
+    public boolean isSuccess()
+    {
+        return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
+    }
+
+    /**
+     * 是否为警告消息
+     *
+     * @return 结果
+     */
+    public boolean isWarn()
+    {
+        return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG));
+    }
+
+    /**
+     * 是否为错误消息
+     *
+     * @return 结果
+     */
+    public boolean isError()
+    {
+        return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
+    }
+
+    /**
+     * 方便链式调用
+     *
+     * @param key 键
+     * @param value 值
+     * @return 数据对象
+     */
+    @Override
+    public AjaxResult put(String key, Object value)
+    {
+        super.put(key, value);
+        return this;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java
new file mode 100644
index 0000000..15bf66b
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java
@@ -0,0 +1,118 @@
+package com.ruoyi.common.core.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+/**
+ * Entity基类
+ * 
+ * @author ruoyi
+ */
+public class BaseEntity implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 搜索值 */
+    @JsonIgnore
+    private String searchValue;
+
+    /** 创建者 */
+    private String createBy;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+
+    /** 请求参数 */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private Map<String, Object> params;
+
+    public String getSearchValue()
+    {
+        return searchValue;
+    }
+
+    public void setSearchValue(String searchValue)
+    {
+        this.searchValue = searchValue;
+    }
+
+    public String getCreateBy()
+    {
+        return createBy;
+    }
+
+    public void setCreateBy(String createBy)
+    {
+        this.createBy = createBy;
+    }
+
+    public Date getCreateTime()
+    {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime)
+    {
+        this.createTime = createTime;
+    }
+
+    public String getUpdateBy()
+    {
+        return updateBy;
+    }
+
+    public void setUpdateBy(String updateBy)
+    {
+        this.updateBy = updateBy;
+    }
+
+    public Date getUpdateTime()
+    {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime)
+    {
+        this.updateTime = updateTime;
+    }
+
+    public String getRemark()
+    {
+        return remark;
+    }
+
+    public void setRemark(String remark)
+    {
+        this.remark = remark;
+    }
+
+    public Map<String, Object> getParams()
+    {
+        if (params == null)
+        {
+            params = new HashMap<>();
+        }
+        return params;
+    }
+
+    public void setParams(Map<String, Object> params)
+    {
+        this.params = params;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java
new file mode 100644
index 0000000..ef15802
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java
@@ -0,0 +1,115 @@
+package com.ruoyi.common.core.domain;
+
+import java.io.Serializable;
+import com.ruoyi.common.constant.HttpStatus;
+
+/**
+ * 响应信息主体
+ *
+ * @author ruoyi
+ */
+public class R<T> implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 成功 */
+    public static final int SUCCESS = HttpStatus.SUCCESS;
+
+    /** 失败 */
+    public static final int FAIL = HttpStatus.ERROR;
+
+    private int code;
+
+    private String msg;
+
+    private T data;
+
+    public static <T> R<T> ok()
+    {
+        return restResult(null, SUCCESS, "操作成功");
+    }
+
+    public static <T> R<T> ok(T data)
+    {
+        return restResult(data, SUCCESS, "操作成功");
+    }
+
+    public static <T> R<T> ok(T data, String msg)
+    {
+        return restResult(data, SUCCESS, msg);
+    }
+
+    public static <T> R<T> fail()
+    {
+        return restResult(null, FAIL, "操作失败");
+    }
+
+    public static <T> R<T> fail(String msg)
+    {
+        return restResult(null, FAIL, msg);
+    }
+
+    public static <T> R<T> fail(T data)
+    {
+        return restResult(data, FAIL, "操作失败");
+    }
+
+    public static <T> R<T> fail(T data, String msg)
+    {
+        return restResult(data, FAIL, msg);
+    }
+
+    public static <T> R<T> fail(int code, String msg)
+    {
+        return restResult(null, code, msg);
+    }
+
+    private static <T> R<T> restResult(T data, int code, String msg)
+    {
+        R<T> apiResult = new R<>();
+        apiResult.setCode(code);
+        apiResult.setData(data);
+        apiResult.setMsg(msg);
+        return apiResult;
+    }
+
+    public int getCode()
+    {
+        return code;
+    }
+
+    public void setCode(int code)
+    {
+        this.code = code;
+    }
+
+    public String getMsg()
+    {
+        return msg;
+    }
+
+    public void setMsg(String msg)
+    {
+        this.msg = msg;
+    }
+
+    public T getData()
+    {
+        return data;
+    }
+
+    public void setData(T data)
+    {
+        this.data = data;
+    }
+
+    public static <T> Boolean isError(R<T> ret)
+    {
+        return !isSuccess(ret);
+    }
+
+    public static <T> Boolean isSuccess(R<T> ret)
+    {
+        return R.SUCCESS == ret.getCode();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java
new file mode 100644
index 0000000..a180a18
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java
@@ -0,0 +1,79 @@
+package com.ruoyi.common.core.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tree基类
+ * 
+ * @author ruoyi
+ */
+public class TreeEntity extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 父菜单名称 */
+    private String parentName;
+
+    /** 父菜单ID */
+    private Long parentId;
+
+    /** 显示顺序 */
+    private Integer orderNum;
+
+    /** 祖级列表 */
+    private String ancestors;
+
+    /** 子部门 */
+    private List<?> children = new ArrayList<>();
+
+    public String getParentName()
+    {
+        return parentName;
+    }
+
+    public void setParentName(String parentName)
+    {
+        this.parentName = parentName;
+    }
+
+    public Long getParentId()
+    {
+        return parentId;
+    }
+
+    public void setParentId(Long parentId)
+    {
+        this.parentId = parentId;
+    }
+
+    public Integer getOrderNum()
+    {
+        return orderNum;
+    }
+
+    public void setOrderNum(Integer orderNum)
+    {
+        this.orderNum = orderNum;
+    }
+
+    public String getAncestors()
+    {
+        return ancestors;
+    }
+
+    public void setAncestors(String ancestors)
+    {
+        this.ancestors = ancestors;
+    }
+
+    public List<?> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<?> children)
+    {
+        this.children = children;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java
new file mode 100644
index 0000000..bd835db
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java
@@ -0,0 +1,77 @@
+package com.ruoyi.common.core.domain;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.stream.Collectors;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+
+/**
+ * Treeselect树结构实体类
+ * 
+ * @author ruoyi
+ */
+public class TreeSelect implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 节点ID */
+    private Long id;
+
+    /** 节点名称 */
+    private String label;
+
+    /** 子节点 */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private List<TreeSelect> children;
+
+    public TreeSelect()
+    {
+
+    }
+
+    public TreeSelect(SysDept dept)
+    {
+        this.id = dept.getDeptId();
+        this.label = dept.getDeptName();
+        this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
+    }
+
+    public TreeSelect(SysMenu menu)
+    {
+        this.id = menu.getMenuId();
+        this.label = menu.getMenuName();
+        this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
+    }
+
+    public Long getId()
+    {
+        return id;
+    }
+
+    public void setId(Long id)
+    {
+        this.id = id;
+    }
+
+    public String getLabel()
+    {
+        return label;
+    }
+
+    public void setLabel(String label)
+    {
+        this.label = label;
+    }
+
+    public List<TreeSelect> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<TreeSelect> children)
+    {
+        this.children = children;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
new file mode 100644
index 0000000..37236db
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java
@@ -0,0 +1,203 @@
+package com.ruoyi.common.core.domain.entity;
+
+import java.util.ArrayList;
+import java.util.List;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 部门表 sys_dept
+ * 
+ * @author ruoyi
+ */
+public class SysDept extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 部门ID */
+    private Long deptId;
+
+    /** 父部门ID */
+    private Long parentId;
+
+    /** 祖级列表 */
+    private String ancestors;
+
+    /** 部门名称 */
+    private String deptName;
+
+    /** 显示顺序 */
+    private Integer orderNum;
+
+    /** 负责人 */
+    private String leader;
+
+    /** 联系电话 */
+    private String phone;
+
+    /** 邮箱 */
+    private String email;
+
+    /** 部门状态:0正常,1停用 */
+    private String status;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    private String delFlag;
+
+    /** 父部门名称 */
+    private String parentName;
+    
+    /** 子部门 */
+    private List<SysDept> children = new ArrayList<SysDept>();
+
+    public Long getDeptId()
+    {
+        return deptId;
+    }
+
+    public void setDeptId(Long deptId)
+    {
+        this.deptId = deptId;
+    }
+
+    public Long getParentId()
+    {
+        return parentId;
+    }
+
+    public void setParentId(Long parentId)
+    {
+        this.parentId = parentId;
+    }
+
+    public String getAncestors()
+    {
+        return ancestors;
+    }
+
+    public void setAncestors(String ancestors)
+    {
+        this.ancestors = ancestors;
+    }
+
+    @NotBlank(message = "部门名称不能为空")
+    @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符")
+    public String getDeptName()
+    {
+        return deptName;
+    }
+
+    public void setDeptName(String deptName)
+    {
+        this.deptName = deptName;
+    }
+
+    @NotNull(message = "显示顺序不能为空")
+    public Integer getOrderNum()
+    {
+        return orderNum;
+    }
+
+    public void setOrderNum(Integer orderNum)
+    {
+        this.orderNum = orderNum;
+    }
+
+    public String getLeader()
+    {
+        return leader;
+    }
+
+    public void setLeader(String leader)
+    {
+        this.leader = leader;
+    }
+
+    @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符")
+    public String getPhone()
+    {
+        return phone;
+    }
+
+    public void setPhone(String phone)
+    {
+        this.phone = phone;
+    }
+
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    public String getEmail()
+    {
+        return email;
+    }
+
+    public void setEmail(String email)
+    {
+        this.email = email;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public String getDelFlag()
+    {
+        return delFlag;
+    }
+
+    public void setDelFlag(String delFlag)
+    {
+        this.delFlag = delFlag;
+    }
+
+    public String getParentName()
+    {
+        return parentName;
+    }
+
+    public void setParentName(String parentName)
+    {
+        this.parentName = parentName;
+    }
+
+    public List<SysDept> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<SysDept> children)
+    {
+        this.children = children;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("deptId", getDeptId())
+            .append("parentId", getParentId())
+            .append("ancestors", getAncestors())
+            .append("deptName", getDeptName())
+            .append("orderNum", getOrderNum())
+            .append("leader", getLeader())
+            .append("phone", getPhone())
+            .append("email", getEmail())
+            .append("status", getStatus())
+            .append("delFlag", getDelFlag())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .toString();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
new file mode 100644
index 0000000..6b035a6
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java
@@ -0,0 +1,176 @@
+package com.ruoyi.common.core.domain.entity;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 字典数据表 sys_dict_data
+ * 
+ * @author ruoyi
+ */
+public class SysDictData extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 字典编码 */
+    @Excel(name = "字典编码", cellType = ColumnType.NUMERIC)
+    private Long dictCode;
+
+    /** 字典排序 */
+    @Excel(name = "字典排序", cellType = ColumnType.NUMERIC)
+    private Long dictSort;
+
+    /** 字典标签 */
+    @Excel(name = "字典标签")
+    private String dictLabel;
+
+    /** 字典键值 */
+    @Excel(name = "字典键值")
+    private String dictValue;
+
+    /** 字典类型 */
+    @Excel(name = "字典类型")
+    private String dictType;
+
+    /** 样式属性(其他样式扩展) */
+    private String cssClass;
+
+    /** 表格字典样式 */
+    private String listClass;
+
+    /** 是否默认(Y是 N否) */
+    @Excel(name = "是否默认", readConverterExp = "Y=是,N=否")
+    private String isDefault;
+
+    /** 状态(0正常 1停用) */
+    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    public Long getDictCode()
+    {
+        return dictCode;
+    }
+
+    public void setDictCode(Long dictCode)
+    {
+        this.dictCode = dictCode;
+    }
+
+    public Long getDictSort()
+    {
+        return dictSort;
+    }
+
+    public void setDictSort(Long dictSort)
+    {
+        this.dictSort = dictSort;
+    }
+
+    @NotBlank(message = "字典标签不能为空")
+    @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符")
+    public String getDictLabel()
+    {
+        return dictLabel;
+    }
+
+    public void setDictLabel(String dictLabel)
+    {
+        this.dictLabel = dictLabel;
+    }
+
+    @NotBlank(message = "字典键值不能为空")
+    @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符")
+    public String getDictValue()
+    {
+        return dictValue;
+    }
+
+    public void setDictValue(String dictValue)
+    {
+        this.dictValue = dictValue;
+    }
+
+    @NotBlank(message = "字典类型不能为空")
+    @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符")
+    public String getDictType()
+    {
+        return dictType;
+    }
+
+    public void setDictType(String dictType)
+    {
+        this.dictType = dictType;
+    }
+
+    @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符")
+    public String getCssClass()
+    {
+        return cssClass;
+    }
+
+    public void setCssClass(String cssClass)
+    {
+        this.cssClass = cssClass;
+    }
+
+    public String getListClass()
+    {
+        return listClass;
+    }
+
+    public void setListClass(String listClass)
+    {
+        this.listClass = listClass;
+    }
+
+    public boolean getDefault()
+    {
+        return UserConstants.YES.equals(this.isDefault);
+    }
+
+    public String getIsDefault()
+    {
+        return isDefault;
+    }
+
+    public void setIsDefault(String isDefault)
+    {
+        this.isDefault = isDefault;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("dictCode", getDictCode())
+            .append("dictSort", getDictSort())
+            .append("dictLabel", getDictLabel())
+            .append("dictValue", getDictValue())
+            .append("dictType", getDictType())
+            .append("cssClass", getCssClass())
+            .append("listClass", getListClass())
+            .append("isDefault", getIsDefault())
+            .append("status", getStatus())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
new file mode 100644
index 0000000..ac5d290
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java
@@ -0,0 +1,96 @@
+package com.ruoyi.common.core.domain.entity;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 字典类型表 sys_dict_type
+ * 
+ * @author ruoyi
+ */
+public class SysDictType extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 字典主键 */
+    @Excel(name = "字典主键", cellType = ColumnType.NUMERIC)
+    private Long dictId;
+
+    /** 字典名称 */
+    @Excel(name = "字典名称")
+    private String dictName;
+
+    /** 字典类型 */
+    @Excel(name = "字典类型")
+    private String dictType;
+
+    /** 状态(0正常 1停用) */
+    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    public Long getDictId()
+    {
+        return dictId;
+    }
+
+    public void setDictId(Long dictId)
+    {
+        this.dictId = dictId;
+    }
+
+    @NotBlank(message = "字典名称不能为空")
+    @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符")
+    public String getDictName()
+    {
+        return dictName;
+    }
+
+    public void setDictName(String dictName)
+    {
+        this.dictName = dictName;
+    }
+
+    @NotBlank(message = "字典类型不能为空")
+    @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符")
+    @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)")
+    public String getDictType()
+    {
+        return dictType;
+    }
+
+    public void setDictType(String dictType)
+    {
+        this.dictType = dictType;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("dictId", getDictId())
+            .append("dictName", getDictName())
+            .append("dictType", getDictType())
+            .append("status", getStatus())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
new file mode 100644
index 0000000..d4ab762
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
@@ -0,0 +1,274 @@
+package com.ruoyi.common.core.domain.entity;
+
+import java.util.ArrayList;
+import java.util.List;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 菜单权限表 sys_menu
+ * 
+ * @author ruoyi
+ */
+public class SysMenu extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 菜单ID */
+    private Long menuId;
+
+    /** 菜单名称 */
+    private String menuName;
+
+    /** 父菜单名称 */
+    private String parentName;
+
+    /** 父菜单ID */
+    private Long parentId;
+
+    /** 显示顺序 */
+    private Integer orderNum;
+
+    /** 路由地址 */
+    private String path;
+
+    /** 组件路径 */
+    private String component;
+
+    /** 路由参数 */
+    private String query;
+
+    /** 路由名称,默认和路由地址相同的驼峰格式(注意:因为vue3版本的router会删除名称相同路由,为避免名字的冲突,特殊情况可以自定义) */
+    private String routeName;
+
+    /** 是否为外链(0是 1否) */
+    private String isFrame;
+
+    /** 是否缓存(0缓存 1不缓存) */
+    private String isCache;
+
+    /** 类型(M目录 C菜单 F按钮) */
+    private String menuType;
+
+    /** 显示状态(0显示 1隐藏) */
+    private String visible;
+
+    /** 菜单状态(0正常 1停用) */
+    private String status;
+
+    /** 权限字符串 */
+    private String perms;
+
+    /** 菜单图标 */
+    private String icon;
+
+    /** 子菜单 */
+    private List<SysMenu> children = new ArrayList<SysMenu>();
+
+    public Long getMenuId()
+    {
+        return menuId;
+    }
+
+    public void setMenuId(Long menuId)
+    {
+        this.menuId = menuId;
+    }
+
+    @NotBlank(message = "菜单名称不能为空")
+    @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符")
+    public String getMenuName()
+    {
+        return menuName;
+    }
+
+    public void setMenuName(String menuName)
+    {
+        this.menuName = menuName;
+    }
+
+    public String getParentName()
+    {
+        return parentName;
+    }
+
+    public void setParentName(String parentName)
+    {
+        this.parentName = parentName;
+    }
+
+    public Long getParentId()
+    {
+        return parentId;
+    }
+
+    public void setParentId(Long parentId)
+    {
+        this.parentId = parentId;
+    }
+
+    @NotNull(message = "显示顺序不能为空")
+    public Integer getOrderNum()
+    {
+        return orderNum;
+    }
+
+    public void setOrderNum(Integer orderNum)
+    {
+        this.orderNum = orderNum;
+    }
+
+    @Size(min = 0, max = 200, message = "路由地址不能超过200个字符")
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    @Size(min = 0, max = 200, message = "组件路径不能超过255个字符")
+    public String getComponent()
+    {
+        return component;
+    }
+
+    public void setComponent(String component)
+    {
+        this.component = component;
+    }
+
+    public String getQuery()
+    {
+        return query;
+    }
+
+    public void setQuery(String query)
+    {
+        this.query = query;
+    }
+
+    public String getRouteName()
+    {
+        return routeName;
+    }
+
+    public void setRouteName(String routeName)
+    {
+        this.routeName = routeName;
+    }
+
+    public String getIsFrame()
+    {
+        return isFrame;
+    }
+
+    public void setIsFrame(String isFrame)
+    {
+        this.isFrame = isFrame;
+    }
+
+    public String getIsCache()
+    {
+        return isCache;
+    }
+
+    public void setIsCache(String isCache)
+    {
+        this.isCache = isCache;
+    }
+
+    @NotBlank(message = "菜单类型不能为空")
+    public String getMenuType()
+    {
+        return menuType;
+    }
+
+    public void setMenuType(String menuType)
+    {
+        this.menuType = menuType;
+    }
+
+    public String getVisible()
+    {
+        return visible;
+    }
+
+    public void setVisible(String visible)
+    {
+        this.visible = visible;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符")
+    public String getPerms()
+    {
+        return perms;
+    }
+
+    public void setPerms(String perms)
+    {
+        this.perms = perms;
+    }
+
+    public String getIcon()
+    {
+        return icon;
+    }
+
+    public void setIcon(String icon)
+    {
+        this.icon = icon;
+    }
+
+    public List<SysMenu> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<SysMenu> children)
+    {
+        this.children = children;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("menuId", getMenuId())
+            .append("menuName", getMenuName())
+            .append("parentId", getParentId())
+            .append("orderNum", getOrderNum())
+            .append("path", getPath())
+            .append("component", getComponent())
+            .append("query", getQuery())
+            .append("routeName", getRouteName())
+            .append("isFrame", getIsFrame())
+            .append("IsCache", getIsCache())
+            .append("menuType", getMenuType())
+            .append("visible", getVisible())
+            .append("status ", getStatus())
+            .append("perms", getPerms())
+            .append("icon", getIcon())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java
new file mode 100644
index 0000000..2f8ed14
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java
@@ -0,0 +1,241 @@
+package com.ruoyi.common.core.domain.entity;
+
+import java.util.Set;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 角色表 sys_role
+ * 
+ * @author ruoyi
+ */
+public class SysRole extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 角色ID */
+    @Excel(name = "角色序号", cellType = ColumnType.NUMERIC)
+    private Long roleId;
+
+    /** 角色名称 */
+    @Excel(name = "角色名称")
+    private String roleName;
+
+    /** 角色权限 */
+    @Excel(name = "角色权限")
+    private String roleKey;
+
+    /** 角色排序 */
+    @Excel(name = "角色排序")
+    private Integer roleSort;
+
+    /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */
+    @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
+    private String dataScope;
+
+    /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
+    private boolean menuCheckStrictly;
+
+    /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */
+    private boolean deptCheckStrictly;
+
+    /** 角色状态(0正常 1停用) */
+    @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    private String delFlag;
+
+    /** 用户是否存在此角色标识 默认不存在 */
+    private boolean flag = false;
+
+    /** 菜单组 */
+    private Long[] menuIds;
+
+    /** 部门组(数据权限) */
+    private Long[] deptIds;
+
+    /** 角色菜单权限 */
+    private Set<String> permissions;
+
+    public SysRole()
+    {
+
+    }
+
+    public SysRole(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    public Long getRoleId()
+    {
+        return roleId;
+    }
+
+    public void setRoleId(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    public boolean isAdmin()
+    {
+        return isAdmin(this.roleId);
+    }
+
+    public static boolean isAdmin(Long roleId)
+    {
+        return roleId != null && 1L == roleId;
+    }
+
+    @NotBlank(message = "角色名称不能为空")
+    @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符")
+    public String getRoleName()
+    {
+        return roleName;
+    }
+
+    public void setRoleName(String roleName)
+    {
+        this.roleName = roleName;
+    }
+
+    @NotBlank(message = "权限字符不能为空")
+    @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符")
+    public String getRoleKey()
+    {
+        return roleKey;
+    }
+
+    public void setRoleKey(String roleKey)
+    {
+        this.roleKey = roleKey;
+    }
+
+    @NotNull(message = "显示顺序不能为空")
+    public Integer getRoleSort()
+    {
+        return roleSort;
+    }
+
+    public void setRoleSort(Integer roleSort)
+    {
+        this.roleSort = roleSort;
+    }
+
+    public String getDataScope()
+    {
+        return dataScope;
+    }
+
+    public void setDataScope(String dataScope)
+    {
+        this.dataScope = dataScope;
+    }
+
+    public boolean isMenuCheckStrictly()
+    {
+        return menuCheckStrictly;
+    }
+
+    public void setMenuCheckStrictly(boolean menuCheckStrictly)
+    {
+        this.menuCheckStrictly = menuCheckStrictly;
+    }
+
+    public boolean isDeptCheckStrictly()
+    {
+        return deptCheckStrictly;
+    }
+
+    public void setDeptCheckStrictly(boolean deptCheckStrictly)
+    {
+        this.deptCheckStrictly = deptCheckStrictly;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public String getDelFlag()
+    {
+        return delFlag;
+    }
+
+    public void setDelFlag(String delFlag)
+    {
+        this.delFlag = delFlag;
+    }
+
+    public boolean isFlag()
+    {
+        return flag;
+    }
+
+    public void setFlag(boolean flag)
+    {
+        this.flag = flag;
+    }
+
+    public Long[] getMenuIds()
+    {
+        return menuIds;
+    }
+
+    public void setMenuIds(Long[] menuIds)
+    {
+        this.menuIds = menuIds;
+    }
+
+    public Long[] getDeptIds()
+    {
+        return deptIds;
+    }
+
+    public void setDeptIds(Long[] deptIds)
+    {
+        this.deptIds = deptIds;
+    }
+
+    public Set<String> getPermissions()
+    {
+        return permissions;
+    }
+
+    public void setPermissions(Set<String> permissions)
+    {
+        this.permissions = permissions;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("roleId", getRoleId())
+            .append("roleName", getRoleName())
+            .append("roleKey", getRoleKey())
+            .append("roleSort", getRoleSort())
+            .append("dataScope", getDataScope())
+            .append("menuCheckStrictly", isMenuCheckStrictly())
+            .append("deptCheckStrictly", isDeptCheckStrictly())
+            .append("status", getStatus())
+            .append("delFlag", getDelFlag())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
new file mode 100644
index 0000000..25fc65d
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -0,0 +1,324 @@
+package com.ruoyi.common.core.domain.entity;
+
+import java.util.Date;
+import java.util.List;
+import jakarta.validation.constraints.*;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.annotation.Excel.Type;
+import com.ruoyi.common.annotation.Excels;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.xss.Xss;
+
+/**
+ * 用户对象 sys_user
+ * 
+ * @author ruoyi
+ */
+public class SysUser extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 用户ID */
+    @Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号")
+    private Long userId;
+
+    /** 部门ID */
+    @Excel(name = "部门编号", type = Type.IMPORT)
+    private Long deptId;
+
+    /** 用户账号 */
+    @Excel(name = "登录名称")
+    private String userName;
+
+    /** 用户昵称 */
+    @Excel(name = "用户名称")
+    private String nickName;
+
+    /** 用户邮箱 */
+    @Excel(name = "用户邮箱")
+    private String email;
+
+    /** 手机号码 */
+    @Excel(name = "手机号码", cellType = ColumnType.TEXT)
+    private String phonenumber;
+
+    /** 用户性别 */
+    @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
+    private String sex;
+
+    /** 用户头像 */
+    private String avatar;
+
+    /** 密码 */
+    private String password;
+
+    /** 帐号状态(0正常 1停用) */
+    @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    private String delFlag;
+
+    /** 最后登录IP */
+    @Excel(name = "最后登录IP", type = Type.EXPORT)
+    private String loginIp;
+
+    /** 最后登录时间 */
+    @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
+    private Date loginDate;
+
+    /** 部门对象 */
+    @Excels({
+        @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
+        @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
+    })
+    private SysDept dept;
+
+    /** 角色对象 */
+    private List<SysRole> roles;
+
+    /** 角色组 */
+    private Long[] roleIds;
+
+    /** 岗位组 */
+    private Long[] postIds;
+
+    /** 角色ID */
+    private Long roleId;
+
+    public SysUser()
+    {
+
+    }
+
+    public SysUser(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public boolean isAdmin()
+    {
+        return isAdmin(this.userId);
+    }
+
+    public static boolean isAdmin(Long userId)
+    {
+        return userId != null && 1L == userId;
+    }
+
+    public Long getDeptId()
+    {
+        return deptId;
+    }
+
+    public void setDeptId(Long deptId)
+    {
+        this.deptId = deptId;
+    }
+
+    @Xss(message = "用户昵称不能包含脚本字符")
+    @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
+    public String getNickName()
+    {
+        return nickName;
+    }
+
+    public void setNickName(String nickName)
+    {
+        this.nickName = nickName;
+    }
+
+    @Xss(message = "用户账号不能包含脚本字符")
+    @NotBlank(message = "用户账号不能为空")
+    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public void setUserName(String userName)
+    {
+        this.userName = userName;
+    }
+
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    public String getEmail()
+    {
+        return email;
+    }
+
+    public void setEmail(String email)
+    {
+        this.email = email;
+    }
+
+    @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
+    public String getPhonenumber()
+    {
+        return phonenumber;
+    }
+
+    public void setPhonenumber(String phonenumber)
+    {
+        this.phonenumber = phonenumber;
+    }
+
+    public String getSex()
+    {
+        return sex;
+    }
+
+    public void setSex(String sex)
+    {
+        this.sex = sex;
+    }
+
+    public String getAvatar()
+    {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar)
+    {
+        this.avatar = avatar;
+    }
+
+    public String getPassword()
+    {
+        return password;
+    }
+
+    public void setPassword(String password)
+    {
+        this.password = password;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public String getDelFlag()
+    {
+        return delFlag;
+    }
+
+    public void setDelFlag(String delFlag)
+    {
+        this.delFlag = delFlag;
+    }
+
+    public String getLoginIp()
+    {
+        return loginIp;
+    }
+
+    public void setLoginIp(String loginIp)
+    {
+        this.loginIp = loginIp;
+    }
+
+    public Date getLoginDate()
+    {
+        return loginDate;
+    }
+
+    public void setLoginDate(Date loginDate)
+    {
+        this.loginDate = loginDate;
+    }
+
+    public SysDept getDept()
+    {
+        return dept;
+    }
+
+    public void setDept(SysDept dept)
+    {
+        this.dept = dept;
+    }
+
+    public List<SysRole> getRoles()
+    {
+        return roles;
+    }
+
+    public void setRoles(List<SysRole> roles)
+    {
+        this.roles = roles;
+    }
+
+    public Long[] getRoleIds()
+    {
+        return roleIds;
+    }
+
+    public void setRoleIds(Long[] roleIds)
+    {
+        this.roleIds = roleIds;
+    }
+
+    public Long[] getPostIds()
+    {
+        return postIds;
+    }
+
+    public void setPostIds(Long[] postIds)
+    {
+        this.postIds = postIds;
+    }
+
+    public Long getRoleId()
+    {
+        return roleId;
+    }
+
+    public void setRoleId(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("userId", getUserId())
+            .append("deptId", getDeptId())
+            .append("userName", getUserName())
+            .append("nickName", getNickName())
+            .append("email", getEmail())
+            .append("phonenumber", getPhonenumber())
+            .append("sex", getSex())
+            .append("avatar", getAvatar())
+            .append("password", getPassword())
+            .append("status", getStatus())
+            .append("delFlag", getDelFlag())
+            .append("loginIp", getLoginIp())
+            .append("loginDate", getLoginDate())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .append("dept", getDept())
+            .toString();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
new file mode 100644
index 0000000..b5bc8c8
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
@@ -0,0 +1,69 @@
+package com.ruoyi.common.core.domain.model;
+
+/**
+ * 用户登录对象
+ * 
+ * @author ruoyi
+ */
+public class LoginBody
+{
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    private String password;
+
+    /**
+     * 验证码
+     */
+    private String code;
+
+    /**
+     * 唯一标识
+     */
+    private String uuid;
+
+    public String getUsername()
+    {
+        return username;
+    }
+
+    public void setUsername(String username)
+    {
+        this.username = username;
+    }
+
+    public String getPassword()
+    {
+        return password;
+    }
+
+    public void setPassword(String password)
+    {
+        this.password = password;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public void setCode(String code)
+    {
+        this.code = code;
+    }
+
+    public String getUuid()
+    {
+        return uuid;
+    }
+
+    public void setUuid(String uuid)
+    {
+        this.uuid = uuid;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java
new file mode 100644
index 0000000..670e6b3
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java
@@ -0,0 +1,266 @@
+package com.ruoyi.common.core.domain.model;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ * 
+ * @author ruoyi
+ */
+public class LoginUser implements UserDetails
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 权限列表
+     */
+    private Set<String> permissions;
+
+    /**
+     * 用户信息
+     */
+    private SysUser user;
+
+    public LoginUser()
+    {
+    }
+
+    public LoginUser(SysUser user, Set<String> permissions)
+    {
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions)
+    {
+        this.userId = userId;
+        this.deptId = deptId;
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public Long getDeptId()
+    {
+        return deptId;
+    }
+
+    public void setDeptId(Long deptId)
+    {
+        this.deptId = deptId;
+    }
+
+    public String getToken()
+    {
+        return token;
+    }
+
+    public void setToken(String token)
+    {
+        this.token = token;
+    }
+
+    @JSONField(serialize = false)
+    @Override
+    public String getPassword()
+    {
+        return user.getPassword();
+    }
+
+    @Override
+    public String getUsername()
+    {
+        return user.getUserName();
+    }
+
+    /**
+     * 账户是否未过期,过期无法验证
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isAccountNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 指定用户是否解锁,锁定的用户无法进行身份验证
+     * 
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isAccountNonLocked()
+    {
+        return true;
+    }
+
+    /**
+     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
+     * 
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isCredentialsNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 是否可用 ,禁用的用户不能身份验证
+     * 
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isEnabled()
+    {
+        return true;
+    }
+
+    public Long getLoginTime()
+    {
+        return loginTime;
+    }
+
+    public void setLoginTime(Long loginTime)
+    {
+        this.loginTime = loginTime;
+    }
+
+    public String getIpaddr()
+    {
+        return ipaddr;
+    }
+
+    public void setIpaddr(String ipaddr)
+    {
+        this.ipaddr = ipaddr;
+    }
+
+    public String getLoginLocation()
+    {
+        return loginLocation;
+    }
+
+    public void setLoginLocation(String loginLocation)
+    {
+        this.loginLocation = loginLocation;
+    }
+
+    public String getBrowser()
+    {
+        return browser;
+    }
+
+    public void setBrowser(String browser)
+    {
+        this.browser = browser;
+    }
+
+    public String getOs()
+    {
+        return os;
+    }
+
+    public void setOs(String os)
+    {
+        this.os = os;
+    }
+
+    public Long getExpireTime()
+    {
+        return expireTime;
+    }
+
+    public void setExpireTime(Long expireTime)
+    {
+        this.expireTime = expireTime;
+    }
+
+    public Set<String> getPermissions()
+    {
+        return permissions;
+    }
+
+    public void setPermissions(Set<String> permissions)
+    {
+        this.permissions = permissions;
+    }
+
+    public SysUser getUser()
+    {
+        return user;
+    }
+
+    public void setUser(SysUser user)
+    {
+        this.user = user;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities()
+    {
+        return null;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java
new file mode 100644
index 0000000..868a1fc
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java
@@ -0,0 +1,11 @@
+package com.ruoyi.common.core.domain.model;
+
+/**
+ * 用户注册对象
+ * 
+ * @author ruoyi
+ */
+public class RegisterBody extends LoginBody
+{
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java
new file mode 100644
index 0000000..8966cb4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java
@@ -0,0 +1,101 @@
+package com.ruoyi.common.core.page;
+
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 分页数据
+ * 
+ * @author ruoyi
+ */
+public class PageDomain
+{
+    /** 当前记录起始索引 */
+    private Integer pageNum;
+
+    /** 每页显示记录数 */
+    private Integer pageSize;
+
+    /** 排序列 */
+    private String orderByColumn;
+
+    /** 排序的方向desc或者asc */
+    private String isAsc = "asc";
+
+    /** 分页参数合理化 */
+    private Boolean reasonable = true;
+
+    public String getOrderBy()
+    {
+        if (StringUtils.isEmpty(orderByColumn))
+        {
+            return "";
+        }
+        return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc;
+    }
+
+    public Integer getPageNum()
+    {
+        return pageNum;
+    }
+
+    public void setPageNum(Integer pageNum)
+    {
+        this.pageNum = pageNum;
+    }
+
+    public Integer getPageSize()
+    {
+        return pageSize;
+    }
+
+    public void setPageSize(Integer pageSize)
+    {
+        this.pageSize = pageSize;
+    }
+
+    public String getOrderByColumn()
+    {
+        return orderByColumn;
+    }
+
+    public void setOrderByColumn(String orderByColumn)
+    {
+        this.orderByColumn = orderByColumn;
+    }
+
+    public String getIsAsc()
+    {
+        return isAsc;
+    }
+
+    public void setIsAsc(String isAsc)
+    {
+        if (StringUtils.isNotEmpty(isAsc))
+        {
+            // 兼容前端排序类型
+            if ("ascending".equals(isAsc))
+            {
+                isAsc = "asc";
+            }
+            else if ("descending".equals(isAsc))
+            {
+                isAsc = "desc";
+            }
+            this.isAsc = isAsc;
+        }
+    }
+
+    public Boolean getReasonable()
+    {
+        if (StringUtils.isNull(reasonable))
+        {
+            return Boolean.TRUE;
+        }
+        return reasonable;
+    }
+
+    public void setReasonable(Boolean reasonable)
+    {
+        this.reasonable = reasonable;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java
new file mode 100644
index 0000000..847685b
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java
@@ -0,0 +1,85 @@
+package com.ruoyi.common.core.page;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 表格分页数据对象
+ * 
+ * @author ruoyi
+ */
+public class TableDataInfo implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 总记录数 */
+    private long total;
+
+    /** 列表数据 */
+    private List<?> rows;
+
+    /** 消息状态码 */
+    private int code;
+
+    /** 消息内容 */
+    private String msg;
+
+    /**
+     * 表格数据对象
+     */
+    public TableDataInfo()
+    {
+    }
+
+    /**
+     * 分页
+     * 
+     * @param list 列表数据
+     * @param total 总记录数
+     */
+    public TableDataInfo(List<?> list, int total)
+    {
+        this.rows = list;
+        this.total = total;
+    }
+
+    public long getTotal()
+    {
+        return total;
+    }
+
+    public void setTotal(long total)
+    {
+        this.total = total;
+    }
+
+    public List<?> getRows()
+    {
+        return rows;
+    }
+
+    public void setRows(List<?> rows)
+    {
+        this.rows = rows;
+    }
+
+    public int getCode()
+    {
+        return code;
+    }
+
+    public void setCode(int code)
+    {
+        this.code = code;
+    }
+
+    public String getMsg()
+    {
+        return msg;
+    }
+
+    public void setMsg(String msg)
+    {
+        this.msg = msg;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java
new file mode 100644
index 0000000..8cfecf0
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java
@@ -0,0 +1,56 @@
+package com.ruoyi.common.core.page;
+
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.utils.ServletUtils;
+
+/**
+ * 表格数据处理
+ * 
+ * @author ruoyi
+ */
+public class TableSupport
+{
+    /**
+     * 当前记录起始索引
+     */
+    public static final String PAGE_NUM = "current";
+
+    /**
+     * 每页显示记录数
+     */
+    public static final String PAGE_SIZE = "pageSize";
+
+    /**
+     * 排序列
+     */
+    public static final String ORDER_BY_COLUMN = "orderByColumn";
+
+    /**
+     * 排序的方向 "desc" 或者 "asc".
+     */
+    public static final String IS_ASC = "isAsc";
+
+    /**
+     * 分页参数合理化
+     */
+    public static final String REASONABLE = "reasonable";
+
+    /**
+     * 封装分页对象
+     */
+    public static PageDomain getPageDomain()
+    {
+        PageDomain pageDomain = new PageDomain();
+        pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1));
+        pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10));
+        pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
+        pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
+        pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
+        return pageDomain;
+    }
+
+    public static PageDomain buildPageRequest()
+    {
+        return getPageDomain();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
new file mode 100644
index 0000000..44e80d8
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
@@ -0,0 +1,268 @@
+package com.ruoyi.common.core.redis;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring redis 工具类
+ *
+ * @author ruoyi
+ **/
+@SuppressWarnings(value = { "unchecked", "rawtypes" })
+@Component
+public class RedisCache
+{
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key 缓存的键值
+     * @param value 缓存的值
+     */
+    public <T> void setCacheObject(final String key, final T value)
+    {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key 缓存的键值
+     * @param value 缓存的值
+     * @param timeout 时间
+     * @param timeUnit 时间颗粒度
+     */
+    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
+    {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout)
+    {
+        return expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @param unit 时间单位
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout, final TimeUnit unit)
+    {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 获取有效时间
+     *
+     * @param key Redis键
+     * @return 有效时间
+     */
+    public long getExpire(final String key)
+    {
+        return redisTemplate.getExpire(key);
+    }
+
+    /**
+     * 判断 key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public Boolean hasKey(String key)
+    {
+        return redisTemplate.hasKey(key);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObject(final String key)
+    {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        return operation.get(key);
+    }
+
+    /**
+     * 删除单个对象
+     *
+     * @param key
+     */
+    public boolean deleteObject(final String key)
+    {
+        return redisTemplate.delete(key);
+    }
+
+    /**
+     * 删除集合对象
+     *
+     * @param collection 多个对象
+     * @return
+     */
+    public boolean deleteObject(final Collection collection)
+    {
+        return redisTemplate.delete(collection) > 0;
+    }
+
+    /**
+     * 缓存List数据
+     *
+     * @param key 缓存的键值
+     * @param dataList 待缓存的List数据
+     * @return 缓存的对象
+     */
+    public <T> long setCacheList(final String key, final List<T> dataList)
+    {
+        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
+        return count == null ? 0 : count;
+    }
+
+    /**
+     * 获得缓存的list对象
+     *
+     * @param key 缓存的键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> List<T> getCacheList(final String key)
+    {
+        return redisTemplate.opsForList().range(key, 0, -1);
+    }
+
+    /**
+     * 缓存Set
+     *
+     * @param key 缓存键值
+     * @param dataSet 缓存的数据
+     * @return 缓存数据的对象
+     */
+    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
+    {
+        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
+        Iterator<T> it = dataSet.iterator();
+        while (it.hasNext())
+        {
+            setOperation.add(it.next());
+        }
+        return setOperation;
+    }
+
+    /**
+     * 获得缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> Set<T> getCacheSet(final String key)
+    {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 缓存Map
+     *
+     * @param key
+     * @param dataMap
+     */
+    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
+    {
+        if (dataMap != null) {
+            redisTemplate.opsForHash().putAll(key, dataMap);
+        }
+    }
+
+    /**
+     * 获得缓存的Map
+     *
+     * @param key
+     * @return
+     */
+    public <T> Map<String, T> getCacheMap(final String key)
+    {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @param value 值
+     */
+    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
+    {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+    /**
+     * 获取Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @return Hash中的对象
+     */
+    public <T> T getCacheMapValue(final String key, final String hKey)
+    {
+        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
+        return opsForHash.get(key, hKey);
+    }
+
+    /**
+     * 获取多个Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKeys Hash键集合
+     * @return Hash对象集合
+     */
+    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
+    {
+        return redisTemplate.opsForHash().multiGet(key, hKeys);
+    }
+
+    /**
+     * 删除Hash中的某条数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @return 是否成功
+     */
+    public boolean deleteCacheMapValue(final String key, final String hKey)
+    {
+        return redisTemplate.opsForHash().delete(key, hKey) > 0;
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern)
+    {
+        return redisTemplate.keys(pattern);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java
new file mode 100644
index 0000000..84124aa
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java
@@ -0,0 +1,86 @@
+package com.ruoyi.common.core.text;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 字符集工具类
+ * 
+ * @author ruoyi
+ */
+public class CharsetKit
+{
+    /** ISO-8859-1 */
+    public static final String ISO_8859_1 = "ISO-8859-1";
+    /** UTF-8 */
+    public static final String UTF_8 = "UTF-8";
+    /** GBK */
+    public static final String GBK = "GBK";
+
+    /** ISO-8859-1 */
+    public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1);
+    /** UTF-8 */
+    public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8);
+    /** GBK */
+    public static final Charset CHARSET_GBK = Charset.forName(GBK);
+
+    /**
+     * 转换为Charset对象
+     * 
+     * @param charset 字符集,为空则返回默认字符集
+     * @return Charset
+     */
+    public static Charset charset(String charset)
+    {
+        return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);
+    }
+
+    /**
+     * 转换字符串的字符集编码
+     * 
+     * @param source 字符串
+     * @param srcCharset 源字符集,默认ISO-8859-1
+     * @param destCharset 目标字符集,默认UTF-8
+     * @return 转换后的字符集
+     */
+    public static String convert(String source, String srcCharset, String destCharset)
+    {
+        return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));
+    }
+
+    /**
+     * 转换字符串的字符集编码
+     * 
+     * @param source 字符串
+     * @param srcCharset 源字符集,默认ISO-8859-1
+     * @param destCharset 目标字符集,默认UTF-8
+     * @return 转换后的字符集
+     */
+    public static String convert(String source, Charset srcCharset, Charset destCharset)
+    {
+        if (null == srcCharset)
+        {
+            srcCharset = StandardCharsets.ISO_8859_1;
+        }
+
+        if (null == destCharset)
+        {
+            destCharset = StandardCharsets.UTF_8;
+        }
+
+        if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset))
+        {
+            return source;
+        }
+        return new String(source.getBytes(srcCharset), destCharset);
+    }
+
+    /**
+     * @return 系统字符集编码
+     */
+    public static String systemCharset()
+    {
+        return Charset.defaultCharset().name();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java
new file mode 100644
index 0000000..edf9afc
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java
@@ -0,0 +1,1010 @@
+package com.ruoyi.common.core.text;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.text.NumberFormat;
+import java.util.Set;
+import com.ruoyi.common.utils.StringUtils;
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * 类型转换器
+ *
+ * @author ruoyi
+ */
+public class Convert
+{
+    /**
+     * 转换为字符串<br>
+     * 如果给定的值为null,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static String toStr(Object value, String defaultValue)
+    {
+        if (null == value)
+        {
+            return defaultValue;
+        }
+        if (value instanceof String)
+        {
+            return (String) value;
+        }
+        return value.toString();
+    }
+
+    /**
+     * 转换为字符串<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static String toStr(Object value)
+    {
+        return toStr(value, null);
+    }
+
+    /**
+     * 转换为字符<br>
+     * 如果给定的值为null,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Character toChar(Object value, Character defaultValue)
+    {
+        if (null == value)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Character)
+        {
+            return (Character) value;
+        }
+
+        final String valueStr = toStr(value, null);
+        return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0);
+    }
+
+    /**
+     * 转换为字符<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Character toChar(Object value)
+    {
+        return toChar(value, null);
+    }
+
+    /**
+     * 转换为byte<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Byte toByte(Object value, Byte defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Byte)
+        {
+            return (Byte) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).byteValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Byte.parseByte(valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为byte<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Byte toByte(Object value)
+    {
+        return toByte(value, null);
+    }
+
+    /**
+     * 转换为Short<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Short toShort(Object value, Short defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Short)
+        {
+            return (Short) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).shortValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Short.parseShort(valueStr.trim());
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为Short<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Short toShort(Object value)
+    {
+        return toShort(value, null);
+    }
+
+    /**
+     * 转换为Number<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Number toNumber(Object value, Number defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Number)
+        {
+            return (Number) value;
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return NumberFormat.getInstance().parse(valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为Number<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Number toNumber(Object value)
+    {
+        return toNumber(value, null);
+    }
+
+    /**
+     * 转换为int<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Integer toInt(Object value, Integer defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Integer)
+        {
+            return (Integer) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).intValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Integer.parseInt(valueStr.trim());
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为int<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Integer toInt(Object value)
+    {
+        return toInt(value, null);
+    }
+
+    /**
+     * 转换为Integer数组<br>
+     *
+     * @param str 被转换的值
+     * @return 结果
+     */
+    public static Integer[] toIntArray(String str)
+    {
+        return toIntArray(",", str);
+    }
+
+    /**
+     * 转换为Long数组<br>
+     *
+     * @param str 被转换的值
+     * @return 结果
+     */
+    public static Long[] toLongArray(String str)
+    {
+        return toLongArray(",", str);
+    }
+
+    /**
+     * 转换为Integer数组<br>
+     *
+     * @param split 分隔符
+     * @param split 被转换的值
+     * @return 结果
+     */
+    public static Integer[] toIntArray(String split, String str)
+    {
+        if (StringUtils.isEmpty(str))
+        {
+            return new Integer[] {};
+        }
+        String[] arr = str.split(split);
+        final Integer[] ints = new Integer[arr.length];
+        for (int i = 0; i < arr.length; i++)
+        {
+            final Integer v = toInt(arr[i], 0);
+            ints[i] = v;
+        }
+        return ints;
+    }
+
+    /**
+     * 转换为Long数组<br>
+     *
+     * @param split 分隔符
+     * @param str 被转换的值
+     * @return 结果
+     */
+    public static Long[] toLongArray(String split, String str)
+    {
+        if (StringUtils.isEmpty(str))
+        {
+            return new Long[] {};
+        }
+        String[] arr = str.split(split);
+        final Long[] longs = new Long[arr.length];
+        for (int i = 0; i < arr.length; i++)
+        {
+            final Long v = toLong(arr[i], null);
+            longs[i] = v;
+        }
+        return longs;
+    }
+
+    /**
+     * 转换为String数组<br>
+     *
+     * @param str 被转换的值
+     * @return 结果
+     */
+    public static String[] toStrArray(String str)
+    {
+        if (StringUtils.isEmpty(str))
+        {
+            return new String[] {};
+        }
+        return toStrArray(",", str);
+    }
+
+    /**
+     * 转换为String数组<br>
+     *
+     * @param split 分隔符
+     * @param split 被转换的值
+     * @return 结果
+     */
+    public static String[] toStrArray(String split, String str)
+    {
+        return str.split(split);
+    }
+
+    /**
+     * 转换为long<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Long toLong(Object value, Long defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Long)
+        {
+            return (Long) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).longValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            // 支持科学计数法
+            return new BigDecimal(valueStr.trim()).longValue();
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为long<br>
+     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Long toLong(Object value)
+    {
+        return toLong(value, null);
+    }
+
+    /**
+     * 转换为double<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Double toDouble(Object value, Double defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Double)
+        {
+            return (Double) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).doubleValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            // 支持科学计数法
+            return new BigDecimal(valueStr.trim()).doubleValue();
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为double<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Double toDouble(Object value)
+    {
+        return toDouble(value, null);
+    }
+
+    /**
+     * 转换为Float<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Float toFloat(Object value, Float defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Float)
+        {
+            return (Float) value;
+        }
+        if (value instanceof Number)
+        {
+            return ((Number) value).floatValue();
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Float.parseFloat(valueStr.trim());
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为Float<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Float toFloat(Object value)
+    {
+        return toFloat(value, null);
+    }
+
+    /**
+     * 转换为boolean<br>
+     * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static Boolean toBool(Object value, Boolean defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof Boolean)
+        {
+            return (Boolean) value;
+        }
+        String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        valueStr = valueStr.trim().toLowerCase();
+        switch (valueStr)
+        {
+            case "true":
+            case "yes":
+            case "ok":
+            case "1":
+                return true;
+            case "false":
+            case "no":
+            case "0":
+                return false;
+            default:
+                return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为boolean<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static Boolean toBool(Object value)
+    {
+        return toBool(value, null);
+    }
+
+    /**
+     * 转换为Enum对象<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     *
+     * @param clazz Enum的Class
+     * @param value 值
+     * @param defaultValue 默认值
+     * @return Enum
+     */
+    public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value, E defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (clazz.isAssignableFrom(value.getClass()))
+        {
+            @SuppressWarnings("unchecked")
+            E myE = (E) value;
+            return myE;
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return Enum.valueOf(clazz, valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为Enum对象<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     *
+     * @param clazz Enum的Class
+     * @param value 值
+     * @return Enum
+     */
+    public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value)
+    {
+        return toEnum(clazz, value, null);
+    }
+
+    /**
+     * 转换为BigInteger<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static BigInteger toBigInteger(Object value, BigInteger defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof BigInteger)
+        {
+            return (BigInteger) value;
+        }
+        if (value instanceof Long)
+        {
+            return BigInteger.valueOf((Long) value);
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return new BigInteger(valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为BigInteger<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static BigInteger toBigInteger(Object value)
+    {
+        return toBigInteger(value, null);
+    }
+
+    /**
+     * 转换为BigDecimal<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @param defaultValue 转换错误时的默认值
+     * @return 结果
+     */
+    public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue)
+    {
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        if (value instanceof BigDecimal)
+        {
+            return (BigDecimal) value;
+        }
+        if (value instanceof Long)
+        {
+            return new BigDecimal((Long) value);
+        }
+        if (value instanceof Double)
+        {
+            return BigDecimal.valueOf((Double) value);
+        }
+        if (value instanceof Integer)
+        {
+            return new BigDecimal((Integer) value);
+        }
+        final String valueStr = toStr(value, null);
+        if (StringUtils.isEmpty(valueStr))
+        {
+            return defaultValue;
+        }
+        try
+        {
+            return new BigDecimal(valueStr);
+        }
+        catch (Exception e)
+        {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * 转换为BigDecimal<br>
+     * 如果给定的值为空,或者转换失败,返回默认值<br>
+     * 转换失败不会报错
+     *
+     * @param value 被转换的值
+     * @return 结果
+     */
+    public static BigDecimal toBigDecimal(Object value)
+    {
+        return toBigDecimal(value, null);
+    }
+
+    /**
+     * 将对象转为字符串<br>
+     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+     *
+     * @param obj 对象
+     * @return 字符串
+     */
+    public static String utf8Str(Object obj)
+    {
+        return str(obj, CharsetKit.CHARSET_UTF_8);
+    }
+
+    /**
+     * 将对象转为字符串<br>
+     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+     *
+     * @param obj 对象
+     * @param charsetName 字符集
+     * @return 字符串
+     */
+    public static String str(Object obj, String charsetName)
+    {
+        return str(obj, Charset.forName(charsetName));
+    }
+
+    /**
+     * 将对象转为字符串<br>
+     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
+     *
+     * @param obj 对象
+     * @param charset 字符集
+     * @return 字符串
+     */
+    public static String str(Object obj, Charset charset)
+    {
+        if (null == obj)
+        {
+            return null;
+        }
+
+        if (obj instanceof String)
+        {
+            return (String) obj;
+        }
+        else if (obj instanceof byte[])
+        {
+            return str((byte[]) obj, charset);
+        }
+        else if (obj instanceof Byte[])
+        {
+            byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj);
+            return str(bytes, charset);
+        }
+        else if (obj instanceof ByteBuffer)
+        {
+            return str((ByteBuffer) obj, charset);
+        }
+        return obj.toString();
+    }
+
+    /**
+     * 将byte数组转为字符串
+     *
+     * @param bytes byte数组
+     * @param charset 字符集
+     * @return 字符串
+     */
+    public static String str(byte[] bytes, String charset)
+    {
+        return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset));
+    }
+
+    /**
+     * 解码字节码
+     *
+     * @param data 字符串
+     * @param charset 字符集,如果此字段为空,则解码的结果取决于平台
+     * @return 解码后的字符串
+     */
+    public static String str(byte[] data, Charset charset)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+
+        if (null == charset)
+        {
+            return new String(data);
+        }
+        return new String(data, charset);
+    }
+
+    /**
+     * 将编码的byteBuffer数据转换为字符串
+     *
+     * @param data 数据
+     * @param charset 字符集,如果为空使用当前系统字符集
+     * @return 字符串
+     */
+    public static String str(ByteBuffer data, String charset)
+    {
+        if (data == null)
+        {
+            return null;
+        }
+
+        return str(data, Charset.forName(charset));
+    }
+
+    /**
+     * 将编码的byteBuffer数据转换为字符串
+     *
+     * @param data 数据
+     * @param charset 字符集,如果为空使用当前系统字符集
+     * @return 字符串
+     */
+    public static String str(ByteBuffer data, Charset charset)
+    {
+        if (null == charset)
+        {
+            charset = Charset.defaultCharset();
+        }
+        return charset.decode(data).toString();
+    }
+
+    // ----------------------------------------------------------------------- 全角半角转换
+    /**
+     * 半角转全角
+     *
+     * @param input String.
+     * @return 全角字符串.
+     */
+    public static String toSBC(String input)
+    {
+        return toSBC(input, null);
+    }
+
+    /**
+     * 半角转全角
+     *
+     * @param input String
+     * @param notConvertSet 不替换的字符集合
+     * @return 全角字符串.
+     */
+    public static String toSBC(String input, Set<Character> notConvertSet)
+    {
+        char[] c = input.toCharArray();
+        for (int i = 0; i < c.length; i++)
+        {
+            if (null != notConvertSet && notConvertSet.contains(c[i]))
+            {
+                // 跳过不替换的字符
+                continue;
+            }
+
+            if (c[i] == ' ')
+            {
+                c[i] = '\u3000';
+            }
+            else if (c[i] < '\177')
+            {
+                c[i] = (char) (c[i] + 65248);
+
+            }
+        }
+        return new String(c);
+    }
+
+    /**
+     * 全角转半角
+     *
+     * @param input String.
+     * @return 半角字符串
+     */
+    public static String toDBC(String input)
+    {
+        return toDBC(input, null);
+    }
+
+    /**
+     * 替换全角为半角
+     *
+     * @param text 文本
+     * @param notConvertSet 不替换的字符集合
+     * @return 替换后的字符
+     */
+    public static String toDBC(String text, Set<Character> notConvertSet)
+    {
+        char[] c = text.toCharArray();
+        for (int i = 0; i < c.length; i++)
+        {
+            if (null != notConvertSet && notConvertSet.contains(c[i]))
+            {
+                // 跳过不替换的字符
+                continue;
+            }
+
+            if (c[i] == '\u3000')
+            {
+                c[i] = ' ';
+            }
+            else if (c[i] > '\uFF00' && c[i] < '\uFF5F')
+            {
+                c[i] = (char) (c[i] - 65248);
+            }
+        }
+        String returnString = new String(c);
+
+        return returnString;
+    }
+
+    /**
+     * 数字金额大写转换 先写个完整的然后将如零拾替换成零
+     *
+     * @param n 数字
+     * @return 中文大写数字
+     */
+    public static String digitUppercase(double n)
+    {
+        String[] fraction = { "角", "分" };
+        String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };
+        String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } };
+
+        String head = n < 0 ? "负" : "";
+        n = Math.abs(n);
+
+        String s = "";
+        for (int i = 0; i < fraction.length; i++)
+        {
+            // 优化double计算精度丢失问题
+            BigDecimal nNum = new BigDecimal(n);
+            BigDecimal decimal = new BigDecimal(10);
+            BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN);
+            double d = scale.doubleValue();
+            s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
+        }
+        if (s.length() < 1)
+        {
+            s = "整";
+        }
+        int integerPart = (int) Math.floor(n);
+
+        for (int i = 0; i < unit[0].length && integerPart > 0; i++)
+        {
+            String p = "";
+            for (int j = 0; j < unit[1].length && n > 0; j++)
+            {
+                p = digit[integerPart % 10] + unit[1][j] + p;
+                integerPart = integerPart / 10;
+            }
+            s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s;
+        }
+        return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整");
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java
new file mode 100644
index 0000000..c78ac77
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java
@@ -0,0 +1,92 @@
+package com.ruoyi.common.core.text;
+
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 字符串格式化
+ * 
+ * @author ruoyi
+ */
+public class StrFormatter
+{
+    public static final String EMPTY_JSON = "{}";
+    public static final char C_BACKSLASH = '\\';
+    public static final char C_DELIM_START = '{';
+    public static final char C_DELIM_END = '}';
+
+    /**
+     * 格式化字符串<br>
+     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
+     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
+     * 例:<br>
+     * 通常使用:format("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 strPattern 字符串模板
+     * @param argArray 参数列表
+     * @return 结果
+     */
+    public static String format(final String strPattern, final Object... argArray)
+    {
+        if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray))
+        {
+            return strPattern;
+        }
+        final int strPatternLength = strPattern.length();
+
+        // 初始化定义好的长度以获得更好的性能
+        StringBuilder sbuf = new StringBuilder(strPatternLength + 50);
+
+        int handledPosition = 0;
+        int delimIndex;// 占位符所在位置
+        for (int argIndex = 0; argIndex < argArray.length; argIndex++)
+        {
+            delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition);
+            if (delimIndex == -1)
+            {
+                if (handledPosition == 0)
+                {
+                    return strPattern;
+                }
+                else
+                { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果
+                    sbuf.append(strPattern, handledPosition, strPatternLength);
+                    return sbuf.toString();
+                }
+            }
+            else
+            {
+                if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH)
+                {
+                    if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH)
+                    {
+                        // 转义符之前还有一个转义符,占位符依旧有效
+                        sbuf.append(strPattern, handledPosition, delimIndex - 1);
+                        sbuf.append(Convert.utf8Str(argArray[argIndex]));
+                        handledPosition = delimIndex + 2;
+                    }
+                    else
+                    {
+                        // 占位符被转义
+                        argIndex--;
+                        sbuf.append(strPattern, handledPosition, delimIndex - 1);
+                        sbuf.append(C_DELIM_START);
+                        handledPosition = delimIndex + 1;
+                    }
+                }
+                else
+                {
+                    // 正常占位符
+                    sbuf.append(strPattern, handledPosition, delimIndex);
+                    sbuf.append(Convert.utf8Str(argArray[argIndex]));
+                    handledPosition = delimIndex + 2;
+                }
+            }
+        }
+        // 加入最后一个占位符后所有的字符
+        sbuf.append(strPattern, handledPosition, strPattern.length());
+
+        return sbuf.toString();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java
new file mode 100644
index 0000000..10b7306
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java
@@ -0,0 +1,20 @@
+package com.ruoyi.common.enums;
+
+/**
+ * 操作状态
+ * 
+ * @author ruoyi
+ *
+ */
+public enum BusinessStatus
+{
+    /**
+     * 成功
+     */
+    SUCCESS,
+
+    /**
+     * 失败
+     */
+    FAIL,
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java
new file mode 100644
index 0000000..2e17c4a
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java
@@ -0,0 +1,59 @@
+package com.ruoyi.common.enums;
+
+/**
+ * 业务操作类型
+ * 
+ * @author ruoyi
+ */
+public enum BusinessType
+{
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 新增
+     */
+    INSERT,
+
+    /**
+     * 修改
+     */
+    UPDATE,
+
+    /**
+     * 删除
+     */
+    DELETE,
+
+    /**
+     * 授权
+     */
+    GRANT,
+
+    /**
+     * 导出
+     */
+    EXPORT,
+
+    /**
+     * 导入
+     */
+    IMPORT,
+
+    /**
+     * 强退
+     */
+    FORCE,
+
+    /**
+     * 生成代码
+     */
+    GENCODE,
+    
+    /**
+     * 清空数据
+     */
+    CLEAN,
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java
new file mode 100644
index 0000000..0d945be
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java
@@ -0,0 +1,19 @@
+package com.ruoyi.common.enums;
+
+/**
+ * 数据源
+ * 
+ * @author ruoyi
+ */
+public enum DataSourceType
+{
+    /**
+     * 主库
+     */
+    MASTER,
+
+    /**
+     * 从库
+     */
+    SLAVE
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/DesensitizedType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DesensitizedType.java
new file mode 100644
index 0000000..4508122
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DesensitizedType.java
@@ -0,0 +1,59 @@
+package com.ruoyi.common.enums;
+
+import java.util.function.Function;
+import com.ruoyi.common.utils.DesensitizedUtil;
+
+/**
+ * 脱敏类型
+ *
+ * @author ruoyi
+ */
+public enum DesensitizedType
+{
+    /**
+     * 姓名,第2位星号替换
+     */
+    USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
+
+    /**
+     * 密码,全部字符都用*代替
+     */
+    PASSWORD(DesensitizedUtil::password),
+
+    /**
+     * 身份证,中间10位星号替换
+     */
+    ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{4})", "$1** **** ****$2")),
+
+    /**
+     * 手机号,中间4位星号替换
+     */
+    PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
+
+    /**
+     * 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换
+     */
+    EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")),
+
+    /**
+     * 银行卡号,保留最后4位,其他星号替换
+     */
+    BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")),
+
+    /**
+     * 车牌号码,包含普通车辆、新能源车辆
+     */
+    CAR_LICENSE(DesensitizedUtil::carLicense);
+
+    private final Function<String, String> desensitizer;
+
+    DesensitizedType(Function<String, String> desensitizer)
+    {
+        this.desensitizer = desensitizer;
+    }
+
+    public Function<String, String> desensitizer()
+    {
+        return desensitizer;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java
new file mode 100644
index 0000000..be6f739
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java
@@ -0,0 +1,36 @@
+package com.ruoyi.common.enums;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.lang.Nullable;
+
+/**
+ * 请求方式
+ *
+ * @author ruoyi
+ */
+public enum HttpMethod
+{
+    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
+
+    private static final Map<String, HttpMethod> mappings = new HashMap<>(16);
+
+    static
+    {
+        for (HttpMethod httpMethod : values())
+        {
+            mappings.put(httpMethod.name(), httpMethod);
+        }
+    }
+
+    @Nullable
+    public static HttpMethod resolve(@Nullable String method)
+    {
+        return (method != null ? mappings.get(method) : null);
+    }
+
+    public boolean matches(String method)
+    {
+        return (this == resolve(method));
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java
new file mode 100644
index 0000000..c609fd8
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java
@@ -0,0 +1,20 @@
+package com.ruoyi.common.enums;
+
+/**
+ * 限流类型
+ *
+ * @author ruoyi
+ */
+
+public enum LimitType
+{
+    /**
+     * 默认策略全局限流
+     */
+    DEFAULT,
+
+    /**
+     * 根据请求者IP进行限流
+     */
+    IP
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java
new file mode 100644
index 0000000..bdd143c
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.enums;
+
+/**
+ * 操作人类别
+ * 
+ * @author ruoyi
+ */
+public enum OperatorType
+{
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 后台用户
+     */
+    MANAGE,
+
+    /**
+     * 手机端用户
+     */
+    MOBILE
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java
new file mode 100644
index 0000000..d7ff44a
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java
@@ -0,0 +1,30 @@
+package com.ruoyi.common.enums;
+
+/**
+ * 用户状态
+ * 
+ * @author ruoyi
+ */
+public enum UserStatus
+{
+    OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除");
+
+    private final String code;
+    private final String info;
+
+    UserStatus(String code, String info)
+    {
+        this.code = code;
+        this.info = info;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public String getInfo()
+    {
+        return info;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java
new file mode 100644
index 0000000..f6ad2ab
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java
@@ -0,0 +1,15 @@
+package com.ruoyi.common.exception;
+
+/**
+ * 演示模式异常
+ * 
+ * @author ruoyi
+ */
+public class DemoModeException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    public DemoModeException()
+    {
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java
new file mode 100644
index 0000000..81a71b5
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java
@@ -0,0 +1,58 @@
+package com.ruoyi.common.exception;
+
+/**
+ * 全局异常
+ * 
+ * @author ruoyi
+ */
+public class GlobalException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误明细,内部调试错误
+     *
+     * 和 {@link CommonResult#getDetailMessage()} 一致的设计
+     */
+    private String detailMessage;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public GlobalException()
+    {
+    }
+
+    public GlobalException(String message)
+    {
+        this.message = message;
+    }
+
+    public String getDetailMessage()
+    {
+        return detailMessage;
+    }
+
+    public GlobalException setDetailMessage(String detailMessage)
+    {
+        this.detailMessage = detailMessage;
+        return this;
+    }
+
+    @Override
+    public String getMessage()
+    {
+        return message;
+    }
+
+    public GlobalException setMessage(String message)
+    {
+        this.message = message;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java
new file mode 100644
index 0000000..fcc7ab6
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java
@@ -0,0 +1,74 @@
+package com.ruoyi.common.exception;
+
+/**
+ * 业务异常
+ * 
+ * @author ruoyi
+ */
+public final class ServiceException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误明细,内部调试错误
+     *
+     * 和 {@link CommonResult#getDetailMessage()} 一致的设计
+     */
+    private String detailMessage;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public ServiceException()
+    {
+    }
+
+    public ServiceException(String message)
+    {
+        this.message = message;
+    }
+
+    public ServiceException(String message, Integer code)
+    {
+        this.message = message;
+        this.code = code;
+    }
+
+    public String getDetailMessage()
+    {
+        return detailMessage;
+    }
+
+    @Override
+    public String getMessage()
+    {
+        return message;
+    }
+
+    public Integer getCode()
+    {
+        return code;
+    }
+
+    public ServiceException setMessage(String message)
+    {
+        this.message = message;
+        return this;
+    }
+
+    public ServiceException setDetailMessage(String detailMessage)
+    {
+        this.detailMessage = detailMessage;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java
new file mode 100644
index 0000000..980fa46
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java
@@ -0,0 +1,26 @@
+package com.ruoyi.common.exception;
+
+/**
+ * 工具类异常
+ * 
+ * @author ruoyi
+ */
+public class UtilException extends RuntimeException
+{
+    private static final long serialVersionUID = 8247610319171014183L;
+
+    public UtilException(Throwable e)
+    {
+        super(e.getMessage(), e);
+    }
+
+    public UtilException(String message)
+    {
+        super(message);
+    }
+
+    public UtilException(String message, Throwable throwable)
+    {
+        super(message, throwable);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java
new file mode 100644
index 0000000..b55d72e
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java
@@ -0,0 +1,97 @@
+package com.ruoyi.common.exception.base;
+
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 基础异常
+ * 
+ * @author ruoyi
+ */
+public class BaseException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 所属模块
+     */
+    private String module;
+
+    /**
+     * 错误码
+     */
+    private String code;
+
+    /**
+     * 错误码对应的参数
+     */
+    private Object[] args;
+
+    /**
+     * 错误消息
+     */
+    private String defaultMessage;
+
+    public BaseException(String module, String code, Object[] args, String defaultMessage)
+    {
+        this.module = module;
+        this.code = code;
+        this.args = args;
+        this.defaultMessage = defaultMessage;
+    }
+
+    public BaseException(String module, String code, Object[] args)
+    {
+        this(module, code, args, null);
+    }
+
+    public BaseException(String module, String defaultMessage)
+    {
+        this(module, null, null, defaultMessage);
+    }
+
+    public BaseException(String code, Object[] args)
+    {
+        this(null, code, args, null);
+    }
+
+    public BaseException(String defaultMessage)
+    {
+        this(null, null, null, defaultMessage);
+    }
+
+    @Override
+    public String getMessage()
+    {
+        String message = null;
+        if (!StringUtils.isEmpty(code))
+        {
+            message = MessageUtils.message(code, args);
+        }
+        if (message == null)
+        {
+            message = defaultMessage;
+        }
+        return message;
+    }
+
+    public String getModule()
+    {
+        return module;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public Object[] getArgs()
+    {
+        return args;
+    }
+
+    public String getDefaultMessage()
+    {
+        return defaultMessage;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java
new file mode 100644
index 0000000..871f09b
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java
@@ -0,0 +1,19 @@
+package com.ruoyi.common.exception.file;
+
+import com.ruoyi.common.exception.base.BaseException;
+
+/**
+ * 文件信息异常类
+ * 
+ * @author ruoyi
+ */
+public class FileException extends BaseException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileException(String code, Object[] args)
+    {
+        super("file", code, args, null);
+    }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java
new file mode 100644
index 0000000..70e0ec9
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.file;
+
+/**
+ * 文件名称超长限制异常类
+ * 
+ * @author ruoyi
+ */
+public class FileNameLengthLimitExceededException extends FileException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileNameLengthLimitExceededException(int defaultFileNameLength)
+    {
+        super("upload.filename.exceed.length", new Object[] { defaultFileNameLength });
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java
new file mode 100644
index 0000000..ec6ab05
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.file;
+
+/**
+ * 文件名大小限制异常类
+ * 
+ * @author ruoyi
+ */
+public class FileSizeLimitExceededException extends FileException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileSizeLimitExceededException(long defaultMaxSize)
+    {
+        super("upload.exceed.maxSize", new Object[] { defaultMaxSize });
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java
new file mode 100644
index 0000000..f45e7ef
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java
@@ -0,0 +1,61 @@
+package com.ruoyi.common.exception.file;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * 文件上传异常类
+ * 
+ * @author ruoyi
+ */
+public class FileUploadException extends Exception
+{
+
+    private static final long serialVersionUID = 1L;
+
+    private final Throwable cause;
+
+    public FileUploadException()
+    {
+        this(null, null);
+    }
+
+    public FileUploadException(final String msg)
+    {
+        this(msg, null);
+    }
+
+    public FileUploadException(String msg, Throwable cause)
+    {
+        super(msg);
+        this.cause = cause;
+    }
+
+    @Override
+    public void printStackTrace(PrintStream stream)
+    {
+        super.printStackTrace(stream);
+        if (cause != null)
+        {
+            stream.println("Caused by:");
+            cause.printStackTrace(stream);
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter writer)
+    {
+        super.printStackTrace(writer);
+        if (cause != null)
+        {
+            writer.println("Caused by:");
+            cause.printStackTrace(writer);
+        }
+    }
+
+    @Override
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java
new file mode 100644
index 0000000..011f308
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java
@@ -0,0 +1,80 @@
+package com.ruoyi.common.exception.file;
+
+import java.util.Arrays;
+
+/**
+ * 文件上传 误异常类
+ * 
+ * @author ruoyi
+ */
+public class InvalidExtensionException extends FileUploadException
+{
+    private static final long serialVersionUID = 1L;
+
+    private String[] allowedExtension;
+    private String extension;
+    private String filename;
+
+    public InvalidExtensionException(String[] allowedExtension, String extension, String filename)
+    {
+        super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式");
+        this.allowedExtension = allowedExtension;
+        this.extension = extension;
+        this.filename = filename;
+    }
+
+    public String[] getAllowedExtension()
+    {
+        return allowedExtension;
+    }
+
+    public String getExtension()
+    {
+        return extension;
+    }
+
+    public String getFilename()
+    {
+        return filename;
+    }
+
+    public static class InvalidImageExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidFlashExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidMediaExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidVideoExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java
new file mode 100644
index 0000000..a567b40
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java
@@ -0,0 +1,34 @@
+package com.ruoyi.common.exception.job;
+
+/**
+ * 计划策略异常
+ * 
+ * @author ruoyi
+ */
+public class TaskException extends Exception
+{
+    private static final long serialVersionUID = 1L;
+
+    private Code code;
+
+    public TaskException(String msg, Code code)
+    {
+        this(msg, code, null);
+    }
+
+    public TaskException(String msg, Code code, Exception nestedEx)
+    {
+        super(msg, nestedEx);
+        this.code = code;
+    }
+
+    public Code getCode()
+    {
+        return code;
+    }
+
+    public enum Code
+    {
+        TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java
new file mode 100644
index 0000000..2bf5038
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 黑名单IP异常类
+ * 
+ * @author ruoyi
+ */
+public class BlackListException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public BlackListException()
+    {
+        super("login.blocked", null);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java
new file mode 100644
index 0000000..389dbc7
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 验证码错误异常类
+ * 
+ * @author ruoyi
+ */
+public class CaptchaException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaException()
+    {
+        super("user.jcaptcha.error", null);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java
new file mode 100644
index 0000000..85f9486
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 验证码失效异常类
+ * 
+ * @author ruoyi
+ */
+public class CaptchaExpireException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaExpireException()
+    {
+        super("user.jcaptcha.expire", null);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java
new file mode 100644
index 0000000..c292d70
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java
@@ -0,0 +1,18 @@
+package com.ruoyi.common.exception.user;
+
+import com.ruoyi.common.exception.base.BaseException;
+
+/**
+ * 用户信息异常类
+ * 
+ * @author ruoyi
+ */
+public class UserException extends BaseException
+{
+    private static final long serialVersionUID = 1L;
+
+    public UserException(String code, Object[] args)
+    {
+        super("user", code, args, null);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java
new file mode 100644
index 0000000..eff8181
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 用户不存在异常类
+ * 
+ * @author ruoyi
+ */
+public class UserNotExistsException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public UserNotExistsException()
+    {
+        super("user.not.exists", null);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java
new file mode 100644
index 0000000..a7f3e5f
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 用户密码不正确或不符合规范异常类
+ * 
+ * @author ruoyi
+ */
+public class UserPasswordNotMatchException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public UserPasswordNotMatchException()
+    {
+        super("user.password.not.match", null);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java
new file mode 100644
index 0000000..c887cf1
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 用户错误最大次数异常类
+ * 
+ * @author ruoyi
+ */
+public class UserPasswordRetryLimitExceedException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime)
+    {
+        super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime });
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java
new file mode 100644
index 0000000..e1e431b
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.filter;
+
+import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
+
+/**
+ * 排除JSON敏感属性
+ * 
+ * @author ruoyi
+ */
+public class PropertyPreExcludeFilter extends SimplePropertyPreFilter
+{
+    public PropertyPreExcludeFilter()
+    {
+    }
+
+    public PropertyPreExcludeFilter addExcludes(String... filters)
+    {
+        for (int i = 0; i < filters.length; i++)
+        {
+            this.getExcludes().add(filters[i]);
+        }
+        return this;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java
new file mode 100644
index 0000000..efeded9
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java
@@ -0,0 +1,52 @@
+package com.ruoyi.common.filter;
+
+import java.io.IOException;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.http.MediaType;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * Repeatable 过滤器
+ * 
+ * @author ruoyi
+ */
+public class RepeatableFilter implements Filter
+{
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException
+    {
+        ServletRequest requestWrapper = null;
+        if (request instanceof HttpServletRequest
+                && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
+        {
+            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
+        }
+        if (null == requestWrapper)
+        {
+            chain.doFilter(request, response);
+        }
+        else
+        {
+            chain.doFilter(requestWrapper, response);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java
new file mode 100644
index 0000000..5c6ee08
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java
@@ -0,0 +1,76 @@
+package com.ruoyi.common.filter;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import com.ruoyi.common.utils.http.HttpHelper;
+import com.ruoyi.common.constant.Constants;
+
+/**
+ * 构建可重复读取inputStream的request
+ * 
+ * @author ruoyi
+ */
+public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
+{
+    private final byte[] body;
+
+    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
+    {
+        super(request);
+        request.setCharacterEncoding(Constants.UTF8);
+        response.setCharacterEncoding(Constants.UTF8);
+
+        body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8);
+    }
+
+    @Override
+    public BufferedReader getReader() throws IOException
+    {
+        return new BufferedReader(new InputStreamReader(getInputStream()));
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+        return new ServletInputStream()
+        {
+            @Override
+            public int read() throws IOException
+            {
+                return bais.read();
+            }
+
+            @Override
+            public int available() throws IOException
+            {
+                return body.length;
+            }
+
+            @Override
+            public boolean isFinished()
+            {
+                return false;
+            }
+
+            @Override
+            public boolean isReady()
+            {
+                return false;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener)
+            {
+
+            }
+        };
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java
new file mode 100644
index 0000000..7438fc2
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java
@@ -0,0 +1,75 @@
+package com.ruoyi.common.filter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.enums.HttpMethod;
+
+/**
+ * 防止XSS攻击的过滤器
+ * 
+ * @author ruoyi
+ */
+public class XssFilter implements Filter
+{
+    /**
+     * 排除链接
+     */
+    public List<String> excludes = new ArrayList<>();
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        String tempExcludes = filterConfig.getInitParameter("excludes");
+        if (StringUtils.isNotEmpty(tempExcludes))
+        {
+            String[] urls = tempExcludes.split(",");
+            for (String url : urls)
+            {
+                excludes.add(url);
+            }
+        }
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException
+    {
+        HttpServletRequest req = (HttpServletRequest) request;
+        HttpServletResponse resp = (HttpServletResponse) response;
+        if (handleExcludeURL(req, resp))
+        {
+            chain.doFilter(request, response);
+            return;
+        }
+        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
+        chain.doFilter(xssRequest, response);
+    }
+
+    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response)
+    {
+        String url = request.getServletPath();
+        String method = request.getMethod();
+        // GET DELETE 不过滤
+        if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method))
+        {
+            return true;
+        }
+        return StringUtils.matches(url, excludes);
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java
new file mode 100644
index 0000000..bf7837b
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java
@@ -0,0 +1,111 @@
+package com.ruoyi.common.filter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import org.apache.commons.io.IOUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.html.EscapeUtil;
+
+/**
+ * XSS过滤处理
+ * 
+ * @author ruoyi
+ */
+public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
+{
+    /**
+     * @param request
+     */
+    public XssHttpServletRequestWrapper(HttpServletRequest request)
+    {
+        super(request);
+    }
+
+    @Override
+    public String[] getParameterValues(String name)
+    {
+        String[] values = super.getParameterValues(name);
+        if (values != null)
+        {
+            int length = values.length;
+            String[] escapesValues = new String[length];
+            for (int i = 0; i < length; i++)
+            {
+                // 防xss攻击和过滤前后空格
+                escapesValues[i] = EscapeUtil.clean(values[i]).trim();
+            }
+            return escapesValues;
+        }
+        return super.getParameterValues(name);
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        // 非json类型,直接返回
+        if (!isJsonRequest())
+        {
+            return super.getInputStream();
+        }
+
+        // 为空,直接返回
+        String json = IOUtils.toString(super.getInputStream(), "utf-8");
+        if (StringUtils.isEmpty(json))
+        {
+            return super.getInputStream();
+        }
+
+        // xss过滤
+        json = EscapeUtil.clean(json).trim();
+        byte[] jsonBytes = json.getBytes("utf-8");
+        final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes);
+        return new ServletInputStream()
+        {
+            @Override
+            public boolean isFinished()
+            {
+                return true;
+            }
+
+            @Override
+            public boolean isReady()
+            {
+                return true;
+            }
+
+            @Override
+            public int available() throws IOException
+            {
+                return jsonBytes.length;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener)
+            {
+            }
+
+            @Override
+            public int read() throws IOException
+            {
+                return bis.read();
+            }
+        };
+    }
+
+    /**
+     * 是否是Json请求
+     * 
+     * @param request
+     */
+    public boolean isJsonRequest()
+    {
+        String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
+        return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java
new file mode 100644
index 0000000..b6326c2
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java
@@ -0,0 +1,114 @@
+package com.ruoyi.common.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * 精确的浮点数运算
+ * 
+ * @author ruoyi
+ */
+public class Arith
+{
+
+    /** 默认除法运算精度 */
+    private static final int DEF_DIV_SCALE = 10;
+
+    /** 这个类不能实例化 */
+    private Arith()
+    {
+    }
+
+    /**
+     * 提供精确的加法运算。
+     * @param v1 被加数
+     * @param v2 加数
+     * @return 两个参数的和
+     */
+    public static double add(double v1, double v2)
+    {
+        BigDecimal b1 = new BigDecimal(Double.toString(v1));
+        BigDecimal b2 = new BigDecimal(Double.toString(v2));
+        return b1.add(b2).doubleValue();
+    }
+
+    /**
+     * 提供精确的减法运算。
+     * @param v1 被减数
+     * @param v2 减数
+     * @return 两个参数的差
+     */
+    public static double sub(double v1, double v2)
+    {
+        BigDecimal b1 = new BigDecimal(Double.toString(v1));
+        BigDecimal b2 = new BigDecimal(Double.toString(v2));
+        return b1.subtract(b2).doubleValue();
+    }
+
+    /**
+     * 提供精确的乘法运算。
+     * @param v1 被乘数
+     * @param v2 乘数
+     * @return 两个参数的积
+     */
+    public static double mul(double v1, double v2)
+    {
+        BigDecimal b1 = new BigDecimal(Double.toString(v1));
+        BigDecimal b2 = new BigDecimal(Double.toString(v2));
+        return b1.multiply(b2).doubleValue();
+    }
+
+    /**
+     * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
+     * 小数点以后10位,以后的数字四舍五入。
+     * @param v1 被除数
+     * @param v2 除数
+     * @return 两个参数的商
+     */
+    public static double div(double v1, double v2)
+    {
+        return div(v1, v2, DEF_DIV_SCALE);
+    }
+
+    /**
+     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
+     * 定精度,以后的数字四舍五入。
+     * @param v1 被除数
+     * @param v2 除数
+     * @param scale 表示表示需要精确到小数点以后几位。
+     * @return 两个参数的商
+     */
+    public static double div(double v1, double v2, int scale)
+    {
+        if (scale < 0)
+        {
+            throw new IllegalArgumentException(
+                    "The scale must be a positive integer or zero");
+        }
+        BigDecimal b1 = new BigDecimal(Double.toString(v1));
+        BigDecimal b2 = new BigDecimal(Double.toString(v2));
+        if (b1.compareTo(BigDecimal.ZERO) == 0)
+        {
+            return BigDecimal.ZERO.doubleValue();
+        }
+        return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    /**
+     * 提供精确的小数位四舍五入处理。
+     * @param v 需要四舍五入的数字
+     * @param scale 小数点后保留几位
+     * @return 四舍五入后的结果
+     */
+    public static double round(double v, int scale)
+    {
+        if (scale < 0)
+        {
+            throw new IllegalArgumentException(
+                    "The scale must be a positive integer or zero");
+        }
+        BigDecimal b = new BigDecimal(Double.toString(v));
+        BigDecimal one = BigDecimal.ONE;
+        return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java
new file mode 100644
index 0000000..fb2ae21
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java
@@ -0,0 +1,191 @@
+package com.ruoyi.common.utils;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+/**
+ * 时间工具类
+ * 
+ * @author ruoyi
+ */
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils
+{
+    public static String YYYY = "yyyy";
+
+    public static String YYYY_MM = "yyyy-MM";
+
+    public static String YYYY_MM_DD = "yyyy-MM-dd";
+
+    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    private static String[] parsePatterns = {
+            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", 
+            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+    /**
+     * 获取当前Date型日期
+     * 
+     * @return Date() 当前日期
+     */
+    public static Date getNowDate()
+    {
+        return new Date();
+    }
+
+    /**
+     * 获取当前日期, 默认格式为yyyy-MM-dd
+     * 
+     * @return String
+     */
+    public static String getDate()
+    {
+        return dateTimeNow(YYYY_MM_DD);
+    }
+
+    public static final String getTime()
+    {
+        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+    }
+
+    public static final String dateTimeNow()
+    {
+        return dateTimeNow(YYYYMMDDHHMMSS);
+    }
+
+    public static final String dateTimeNow(final String format)
+    {
+        return parseDateToStr(format, new Date());
+    }
+
+    public static final String dateTime(final Date date)
+    {
+        return parseDateToStr(YYYY_MM_DD, date);
+    }
+
+    public static final String parseDateToStr(final String format, final Date date)
+    {
+        return new SimpleDateFormat(format).format(date);
+    }
+
+    public static final Date dateTime(final String format, final String ts)
+    {
+        try
+        {
+            return new SimpleDateFormat(format).parse(ts);
+        }
+        catch (ParseException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 日期路径 即年/月/日 如2018/08/08
+     */
+    public static final String datePath()
+    {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyy/MM/dd");
+    }
+
+    /**
+     * 日期路径 即年/月/日 如20180808
+     */
+    public static final String dateTime()
+    {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyyMMdd");
+    }
+
+    /**
+     * 日期型字符串转化为日期 格式
+     */
+    public static Date parseDate(Object str)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+        try
+        {
+            return parseDate(str.toString(), parsePatterns);
+        }
+        catch (ParseException e)
+        {
+            return null;
+        }
+    }
+
+    /**
+     * 获取服务器启动时间
+     */
+    public static Date getServerStartDate()
+    {
+        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+        return new Date(time);
+    }
+
+    /**
+     * 计算相差天数
+     */
+    public static int differentDaysByMillisecond(Date date1, Date date2)
+    {
+        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
+    }
+
+    /**
+     * 计算时间差
+     *
+     * @param endDate 最后时间
+     * @param startTime 开始时间
+     * @return 时间差(天/小时/分钟)
+     */
+    public static String timeDistance(Date endDate, Date startTime)
+    {
+        long nd = 1000 * 24 * 60 * 60;
+        long nh = 1000 * 60 * 60;
+        long nm = 1000 * 60;
+        // long ns = 1000;
+        // 获得两个时间的毫秒时间差异
+        long diff = endDate.getTime() - startTime.getTime();
+        // 计算差多少天
+        long day = diff / nd;
+        // 计算差多少小时
+        long hour = diff % nd / nh;
+        // 计算差多少分钟
+        long min = diff % nd % nh / nm;
+        // 计算差多少秒//输出结果
+        // long sec = diff % nd % nh % nm / ns;
+        return day + "天" + hour + "小时" + min + "分钟";
+    }
+
+    /**
+     * 增加 LocalDateTime ==> Date
+     */
+    public static Date toDate(LocalDateTime temporalAccessor)
+    {
+        ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+        return Date.from(zdt.toInstant());
+    }
+
+    /**
+     * 增加 LocalDate ==> Date
+     */
+    public static Date toDate(LocalDate temporalAccessor)
+    {
+        LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+        return Date.from(zdt.toInstant());
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DesensitizedUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DesensitizedUtil.java
new file mode 100644
index 0000000..f8a4c02
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DesensitizedUtil.java
@@ -0,0 +1,49 @@
+package com.ruoyi.common.utils;
+
+/**
+ * 脱敏工具类
+ *
+ * @author ruoyi
+ */
+public class DesensitizedUtil
+{
+    /**
+     * 密码的全部字符都用*代替,比如:******
+     *
+     * @param password 密码
+     * @return 脱敏后的密码
+     */
+    public static String password(String password)
+    {
+        if (StringUtils.isBlank(password))
+        {
+            return StringUtils.EMPTY;
+        }
+        return StringUtils.repeat('*', password.length());
+    }
+
+    /**
+     * 车牌中间用*代替,如果是错误的车牌,不处理
+     *
+     * @param carLicense 完整的车牌号
+     * @return 脱敏后的车牌
+     */
+    public static String carLicense(String carLicense)
+    {
+        if (StringUtils.isBlank(carLicense))
+        {
+            return StringUtils.EMPTY;
+        }
+        // 普通车牌
+        if (carLicense.length() == 7)
+        {
+            carLicense = StringUtils.hide(carLicense, 3, 6);
+        }
+        else if (carLicense.length() == 8)
+        {
+            // 新能源车牌
+            carLicense = StringUtils.hide(carLicense, 3, 7);
+        }
+        return carLicense;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
new file mode 100644
index 0000000..f198462
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
@@ -0,0 +1,239 @@
+package com.ruoyi.common.utils;
+
+import java.util.Collection;
+import java.util.List;
+import com.alibaba.fastjson2.JSONArray;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.spring.SpringUtils;
+
+/**
+ * 字典工具类
+ * 
+ * @author ruoyi
+ */
+public class DictUtils
+{
+    /**
+     * 分隔符
+     */
+    public static final String SEPARATOR = ",";
+
+    /**
+     * 设置字典缓存
+     * 
+     * @param key 参数键
+     * @param dictDatas 字典数据列表
+     */
+    public static void setDictCache(String key, List<SysDictData> dictDatas)
+    {
+        SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);
+    }
+
+    /**
+     * 获取字典缓存
+     * 
+     * @param key 参数键
+     * @return dictDatas 字典数据列表
+     */
+    public static List<SysDictData> getDictCache(String key)
+    {
+        JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
+        if (StringUtils.isNotNull(arrayCache))
+        {
+            return arrayCache.toList(SysDictData.class);
+        }
+        return null;
+    }
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典值
+     * @return 字典标签
+     */
+    public static String getDictLabel(String dictType, String dictValue)
+    {
+        if (StringUtils.isEmpty(dictValue))
+        {
+            return StringUtils.EMPTY;
+        }
+        return getDictLabel(dictType, dictValue, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     * 
+     * @param dictType 字典类型
+     * @param dictLabel 字典标签
+     * @return 字典值
+     */
+    public static String getDictValue(String dictType, String dictLabel)
+    {
+        if (StringUtils.isEmpty(dictLabel))
+        {
+            return StringUtils.EMPTY;
+        }
+        return getDictValue(dictType, dictLabel, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典值
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    public static String getDictLabel(String dictType, String dictValue, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+        if (StringUtils.isNull(datas))
+        {
+            return StringUtils.EMPTY;
+        }
+        if (StringUtils.containsAny(separator, dictValue))
+        {
+            for (SysDictData dict : datas)
+            {
+                for (String value : dictValue.split(separator))
+                {
+                    if (value.equals(dict.getDictValue()))
+                    {
+                        propertyString.append(dict.getDictLabel()).append(separator);
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (SysDictData dict : datas)
+            {
+                if (dictValue.equals(dict.getDictValue()))
+                {
+                    return dict.getDictLabel();
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     * 
+     * @param dictType 字典类型
+     * @param dictLabel 字典标签
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    public static String getDictValue(String dictType, String dictLabel, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+        if (StringUtils.isNull(datas))
+        {
+            return StringUtils.EMPTY;
+        }
+        if (StringUtils.containsAny(separator, dictLabel))
+        {
+            for (SysDictData dict : datas)
+            {
+                for (String label : dictLabel.split(separator))
+                {
+                    if (label.equals(dict.getDictLabel()))
+                    {
+                        propertyString.append(dict.getDictValue()).append(separator);
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (SysDictData dict : datas)
+            {
+                if (dictLabel.equals(dict.getDictLabel()))
+                {
+                    return dict.getDictValue();
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 根据字典类型获取字典所有值
+     *
+     * @param dictType 字典类型
+     * @return 字典值
+     */
+    public static String getDictValues(String dictType)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+        if (StringUtils.isNull(datas))
+        {
+            return StringUtils.EMPTY;
+        }
+        for (SysDictData dict : datas)
+        {
+            propertyString.append(dict.getDictValue()).append(SEPARATOR);
+        }
+        return StringUtils.stripEnd(propertyString.toString(), SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型获取字典所有标签
+     *
+     * @param dictType 字典类型
+     * @return 字典值
+     */
+    public static String getDictLabels(String dictType)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+        if (StringUtils.isNull(datas))
+        {
+            return StringUtils.EMPTY;
+        }
+        for (SysDictData dict : datas)
+        {
+            propertyString.append(dict.getDictLabel()).append(SEPARATOR);
+        }
+        return StringUtils.stripEnd(propertyString.toString(), SEPARATOR);
+    }
+
+    /**
+     * 删除指定字典缓存
+     * 
+     * @param key 字典键
+     */
+    public static void removeDictCache(String key)
+    {
+        SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key));
+    }
+
+    /**
+     * 清空字典缓存
+     */
+    public static void clearDictCache()
+    {
+        Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*");
+        SpringUtils.getBean(RedisCache.class).deleteObject(keys);
+    }
+
+    /**
+     * 设置cache key
+     * 
+     * @param configKey 参数键
+     * @return 缓存键key
+     */
+    public static String getCacheKey(String configKey)
+    {
+        return CacheConstants.SYS_DICT_KEY + configKey;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java
new file mode 100644
index 0000000..214e4a0
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java
@@ -0,0 +1,39 @@
+package com.ruoyi.common.utils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+/**
+ * 错误信息处理类。
+ *
+ * @author ruoyi
+ */
+public class ExceptionUtil
+{
+    /**
+     * 获取exception的详细错误信息。
+     */
+    public static String getExceptionMessage(Throwable e)
+    {
+        StringWriter sw = new StringWriter();
+        e.printStackTrace(new PrintWriter(sw, true));
+        return sw.toString();
+    }
+
+    public static String getRootErrorMessage(Exception e)
+    {
+        Throwable root = ExceptionUtils.getRootCause(e);
+        root = (root == null ? e : root);
+        if (root == null)
+        {
+            return "";
+        }
+        String msg = root.getMessage();
+        if (msg == null)
+        {
+            return "null";
+        }
+        return StringUtils.defaultString(msg);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java
new file mode 100644
index 0000000..0de30c6
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java
@@ -0,0 +1,18 @@
+package com.ruoyi.common.utils;
+
+/**
+ * 处理并记录日志文件
+ * 
+ * @author ruoyi
+ */
+public class LogUtils
+{
+    public static String getBlock(Object msg)
+    {
+        if (msg == null)
+        {
+            msg = "";
+        }
+        return "[" + msg.toString() + "]";
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java
new file mode 100644
index 0000000..7dac75a
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java
@@ -0,0 +1,26 @@
+package com.ruoyi.common.utils;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+import com.ruoyi.common.utils.spring.SpringUtils;
+
+/**
+ * 获取i18n资源文件
+ * 
+ * @author ruoyi
+ */
+public class MessageUtils
+{
+    /**
+     * 根据消息键和参数 获取消息 委托给spring messageSource
+     *
+     * @param code 消息键
+     * @param args 参数
+     * @return 获取国际化翻译值
+     */
+    public static String message(String code, Object... args)
+    {
+        MessageSource messageSource = SpringUtils.getBean(MessageSource.class);
+        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java
new file mode 100644
index 0000000..70e9b08
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java
@@ -0,0 +1,35 @@
+package com.ruoyi.common.utils;
+
+import com.github.pagehelper.PageHelper;
+import com.ruoyi.common.core.page.PageDomain;
+import com.ruoyi.common.core.page.TableSupport;
+import com.ruoyi.common.utils.sql.SqlUtil;
+
+/**
+ * 分页工具类
+ * 
+ * @author ruoyi
+ */
+public class PageUtils extends PageHelper
+{
+    /**
+     * 设置请求分页数据
+     */
+    public static void startPage()
+    {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
+        Boolean reasonable = pageDomain.getReasonable();
+        PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
+    }
+
+    /**
+     * 清理分页的线程变量
+     */
+    public static void clearPage()
+    {
+        PageHelper.clearPage();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
new file mode 100644
index 0000000..0d3ac5f
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
@@ -0,0 +1,178 @@
+package com.ruoyi.common.utils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.util.PatternMatchUtils;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.exception.ServiceException;
+
+/**
+ * 安全服务工具类
+ * 
+ * @author ruoyi
+ */
+public class SecurityUtils
+{
+
+    /**
+     * 用户ID
+     **/
+    public static Long getUserId()
+    {
+        try
+        {
+            return getLoginUser().getUserId();
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取部门ID
+     **/
+    public static Long getDeptId()
+    {
+        try
+        {
+            return getLoginUser().getDeptId();
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取用户账户
+     **/
+    public static String getUsername()
+    {
+        try
+        {
+            return getLoginUser().getUsername();
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取用户
+     **/
+    public static LoginUser getLoginUser()
+    {
+        try
+        {
+            return (LoginUser) getAuthentication().getPrincipal();
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取Authentication
+     */
+    public static Authentication getAuthentication()
+    {
+        return SecurityContextHolder.getContext().getAuthentication();
+    }
+
+    /**
+     * 生成BCryptPasswordEncoder密码
+     *
+     * @param password 密码
+     * @return 加密字符串
+     */
+    public static String encryptPassword(String password)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.encode(password);
+    }
+
+    /**
+     * 判断密码是否相同
+     *
+     * @param rawPassword 真实密码
+     * @param encodedPassword 加密后字符
+     * @return 结果
+     */
+    public static boolean matchesPassword(String rawPassword, String encodedPassword)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 是否为管理员
+     * 
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public static boolean isAdmin(Long userId)
+    {
+        return userId != null && 1L == userId;
+    }
+
+    /**
+     * 验证用户是否具备某权限
+     * 
+     * @param permission 权限字符串
+     * @return 用户是否具备某权限
+     */
+    public static boolean hasPermi(String permission)
+    {
+        return hasPermi(getLoginUser().getPermissions(), permission);
+    }
+
+    /**
+     * 判断是否包含权限
+     * 
+     * @param authorities 权限列表
+     * @param permission 权限字符串
+     * @return 用户是否具备某权限
+     */
+    public static boolean hasPermi(Collection<String> authorities, String permission)
+    {
+        return authorities.stream().filter(StringUtils::hasText)
+                .anyMatch(x -> Constants.ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission));
+    }
+
+    /**
+     * 验证用户是否拥有某个角色
+     * 
+     * @param role 角色标识
+     * @return 用户是否具备某角色
+     */
+    public static boolean hasRole(String role)
+    {
+        List<SysRole> roleList = getLoginUser().getUser().getRoles();
+        Collection<String> roles = roleList.stream().map(SysRole::getRoleKey).collect(Collectors.toSet());
+        return hasRole(roles, role);
+    }
+
+    /**
+     * 判断是否包含角色
+     * 
+     * @param roles 角色列表
+     * @param role 角色
+     * @return 用户是否具备某角色权限
+     */
+    public static boolean hasRole(Collection<String> roles, String role)
+    {
+        return roles.stream().filter(StringUtils::hasText)
+                .anyMatch(x -> Constants.SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role));
+    }
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java
new file mode 100644
index 0000000..5635db7
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java
@@ -0,0 +1,218 @@
+package com.ruoyi.common.utils;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.text.Convert;
+
+/**
+ * 客户端工具类
+ * 
+ * @author ruoyi
+ */
+public class ServletUtils
+{
+    /**
+     * 获取String参数
+     */
+    public static String getParameter(String name)
+    {
+        return getRequest().getParameter(name);
+    }
+
+    /**
+     * 获取String参数
+     */
+    public static String getParameter(String name, String defaultValue)
+    {
+        return Convert.toStr(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 获取Integer参数
+     */
+    public static Integer getParameterToInt(String name)
+    {
+        return Convert.toInt(getRequest().getParameter(name));
+    }
+
+    /**
+     * 获取Integer参数
+     */
+    public static Integer getParameterToInt(String name, Integer defaultValue)
+    {
+        return Convert.toInt(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 获取Boolean参数
+     */
+    public static Boolean getParameterToBool(String name)
+    {
+        return Convert.toBool(getRequest().getParameter(name));
+    }
+
+    /**
+     * 获取Boolean参数
+     */
+    public static Boolean getParameterToBool(String name, Boolean defaultValue)
+    {
+        return Convert.toBool(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 获得所有请求参数
+     *
+     * @param request 请求对象{@link ServletRequest}
+     * @return Map
+     */
+    public static Map<String, String[]> getParams(ServletRequest request)
+    {
+        final Map<String, String[]> map = request.getParameterMap();
+        return Collections.unmodifiableMap(map);
+    }
+
+    /**
+     * 获得所有请求参数
+     *
+     * @param request 请求对象{@link ServletRequest}
+     * @return Map
+     */
+    public static Map<String, String> getParamMap(ServletRequest request)
+    {
+        Map<String, String> params = new HashMap<>();
+        for (Map.Entry<String, String[]> entry : getParams(request).entrySet())
+        {
+            params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
+        }
+        return params;
+    }
+
+    /**
+     * 获取request
+     */
+    public static HttpServletRequest getRequest()
+    {
+        return getRequestAttributes().getRequest();
+    }
+
+    /**
+     * 获取response
+     */
+    public static HttpServletResponse getResponse()
+    {
+        return getRequestAttributes().getResponse();
+    }
+
+    /**
+     * 获取session
+     */
+    public static HttpSession getSession()
+    {
+        return getRequest().getSession();
+    }
+
+    public static ServletRequestAttributes getRequestAttributes()
+    {
+        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+        return (ServletRequestAttributes) attributes;
+    }
+
+    /**
+     * 将字符串渲染到客户端
+     * 
+     * @param response 渲染对象
+     * @param string 待渲染的字符串
+     */
+    public static void renderString(HttpServletResponse response, String string)
+    {
+        try
+        {
+            response.setStatus(200);
+            response.setContentType("application/json");
+            response.setCharacterEncoding("utf-8");
+            response.getWriter().print(string);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 是否是Ajax异步请求
+     * 
+     * @param request
+     */
+    public static boolean isAjaxRequest(HttpServletRequest request)
+    {
+        String accept = request.getHeader("accept");
+        if (accept != null && accept.contains("application/json"))
+        {
+            return true;
+        }
+
+        String xRequestedWith = request.getHeader("X-Requested-With");
+        if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest"))
+        {
+            return true;
+        }
+
+        String uri = request.getRequestURI();
+        if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml"))
+        {
+            return true;
+        }
+
+        String ajax = request.getParameter("__ajax");
+        return StringUtils.inStringIgnoreCase(ajax, "json", "xml");
+    }
+
+    /**
+     * 内容编码
+     * 
+     * @param str 内容
+     * @return 编码后的内容
+     */
+    public static String urlEncode(String str)
+    {
+        try
+        {
+            return URLEncoder.encode(str, Constants.UTF8);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            return StringUtils.EMPTY;
+        }
+    }
+
+    /**
+     * 内容解码
+     * 
+     * @param str 内容
+     * @return 解码后的内容
+     */
+    public static String urlDecode(String str)
+    {
+        try
+        {
+            return URLDecoder.decode(str, Constants.UTF8);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            return StringUtils.EMPTY;
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
new file mode 100644
index 0000000..25c2886
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
@@ -0,0 +1,684 @@
+package com.ruoyi.common.utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.springframework.util.AntPathMatcher;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.text.StrFormatter;
+
+/**
+ * 字符串工具类
+ * 
+ * @author ruoyi
+ */
+public class StringUtils extends org.apache.commons.lang3.StringUtils
+{
+    /** 空字符串 */
+    private static final String NULLSTR = "";
+
+    /** 下划线 */
+    private static final char SEPARATOR = '_';
+
+    /** 星号 */
+    private static final char ASTERISK = '*';
+
+    /**
+     * 获取参数不为空值
+     * 
+     * @param value defaultValue 要判断的value
+     * @return value 返回值
+     */
+    public static <T> T nvl(T value, T defaultValue)
+    {
+        return value != null ? value : defaultValue;
+    }
+
+    /**
+     * * 判断一个Collection是否为空, 包含List,Set,Queue
+     * 
+     * @param coll 要判断的Collection
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Collection<?> coll)
+    {
+        return isNull(coll) || coll.isEmpty();
+    }
+
+    /**
+     * * 判断一个Collection是否非空,包含List,Set,Queue
+     * 
+     * @param coll 要判断的Collection
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Collection<?> coll)
+    {
+        return !isEmpty(coll);
+    }
+
+    /**
+     * * 判断一个对象数组是否为空
+     * 
+     * @param objects 要判断的对象数组
+     ** @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Object[] objects)
+    {
+        return isNull(objects) || (objects.length == 0);
+    }
+
+    /**
+     * * 判断一个对象数组是否非空
+     * 
+     * @param objects 要判断的对象数组
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Object[] objects)
+    {
+        return !isEmpty(objects);
+    }
+
+    /**
+     * * 判断一个Map是否为空
+     * 
+     * @param map 要判断的Map
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Map<?, ?> map)
+    {
+        return isNull(map) || map.isEmpty();
+    }
+
+    /**
+     * * 判断一个Map是否为空
+     * 
+     * @param map 要判断的Map
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Map<?, ?> map)
+    {
+        return !isEmpty(map);
+    }
+
+    /**
+     * * 判断一个字符串是否为空串
+     * 
+     * @param str String
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(String str)
+    {
+        return isNull(str) || NULLSTR.equals(str.trim());
+    }
+
+    /**
+     * * 判断一个字符串是否为非空串
+     * 
+     * @param str String
+     * @return true:非空串 false:空串
+     */
+    public static boolean isNotEmpty(String str)
+    {
+        return !isEmpty(str);
+    }
+
+    /**
+     * * 判断一个对象是否为空
+     * 
+     * @param object Object
+     * @return true:为空 false:非空
+     */
+    public static boolean isNull(Object object)
+    {
+        return object == null;
+    }
+
+    /**
+     * * 判断一个对象是否非空
+     * 
+     * @param object Object
+     * @return true:非空 false:空
+     */
+    public static boolean isNotNull(Object object)
+    {
+        return !isNull(object);
+    }
+
+    /**
+     * * 判断一个对象是否是数组类型(Java基本型别的数组)
+     * 
+     * @param object 对象
+     * @return true:是数组 false:不是数组
+     */
+    public static boolean isArray(Object object)
+    {
+        return isNotNull(object) && object.getClass().isArray();
+    }
+
+    /**
+     * 去空格
+     */
+    public static String trim(String str)
+    {
+        return (str == null ? "" : str.trim());
+    }
+
+    /**
+     * 替换指定字符串的指定区间内字符为"*"
+     *
+     * @param str 字符串
+     * @param startInclude 开始位置(包含)
+     * @param endExclude 结束位置(不包含)
+     * @return 替换后的字符串
+     */
+    public static String hide(CharSequence str, int startInclude, int endExclude)
+    {
+        if (isEmpty(str))
+        {
+            return NULLSTR;
+        }
+        final int strLength = str.length();
+        if (startInclude > strLength)
+        {
+            return NULLSTR;
+        }
+        if (endExclude > strLength)
+        {
+            endExclude = strLength;
+        }
+        if (startInclude > endExclude)
+        {
+            // 如果起始位置大于结束位置,不替换
+            return NULLSTR;
+        }
+        final char[] chars = new char[strLength];
+        for (int i = 0; i < strLength; i++)
+        {
+            if (i >= startInclude && i < endExclude)
+            {
+                chars[i] = ASTERISK;
+            }
+            else
+            {
+                chars[i] = str.charAt(i);
+            }
+        }
+        return new String(chars);
+    }
+
+    /**
+     * 截取字符串
+     * 
+     * @param str 字符串
+     * @param start 开始
+     * @return 结果
+     */
+    public static String substring(final String str, int start)
+    {
+        if (str == null)
+        {
+            return NULLSTR;
+        }
+
+        if (start < 0)
+        {
+            start = str.length() + start;
+        }
+
+        if (start < 0)
+        {
+            start = 0;
+        }
+        if (start > str.length())
+        {
+            return NULLSTR;
+        }
+
+        return str.substring(start);
+    }
+
+    /**
+     * 截取字符串
+     * 
+     * @param str 字符串
+     * @param start 开始
+     * @param end 结束
+     * @return 结果
+     */
+    public static String substring(final String str, int start, int end)
+    {
+        if (str == null)
+        {
+            return NULLSTR;
+        }
+
+        if (end < 0)
+        {
+            end = str.length() + end;
+        }
+        if (start < 0)
+        {
+            start = str.length() + start;
+        }
+
+        if (end > str.length())
+        {
+            end = str.length();
+        }
+
+        if (start > end)
+        {
+            return NULLSTR;
+        }
+
+        if (start < 0)
+        {
+            start = 0;
+        }
+        if (end < 0)
+        {
+            end = 0;
+        }
+
+        return str.substring(start, end);
+    }
+
+    /**
+     * 判断是否为空,并且不是空白字符
+     * 
+     * @param str 要判断的value
+     * @return 结果
+     */
+    public static boolean hasText(String str)
+    {
+        return (str != null && !str.isEmpty() && containsText(str));
+    }
+
+    private static boolean containsText(CharSequence str)
+    {
+        int strLen = str.length();
+        for (int i = 0; i < strLen; i++)
+        {
+            if (!Character.isWhitespace(str.charAt(i)))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 格式化文本, {} 表示占位符<br>
+     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
+     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
+     * 例:<br>
+     * 通常使用:format("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)
+    {
+        if (isEmpty(params) || isEmpty(template))
+        {
+            return template;
+        }
+        return StrFormatter.format(template, params);
+    }
+
+    /**
+     * 是否为http(s)://开头
+     * 
+     * @param link 链接
+     * @return 结果
+     */
+    public static boolean ishttp(String link)
+    {
+        return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
+    }
+
+    /**
+     * 字符串转set
+     * 
+     * @param str 字符串
+     * @param sep 分隔符
+     * @return set集合
+     */
+    public static final Set<String> str2Set(String str, String sep)
+    {
+        return new HashSet<String>(str2List(str, sep, true, false));
+    }
+
+    /**
+     * 字符串转list
+     * 
+     * @param str 字符串
+     * @param sep 分隔符
+     * @param filterBlank 过滤纯空白
+     * @param trim 去掉首尾空白
+     * @return list集合
+     */
+    public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim)
+    {
+        List<String> list = new ArrayList<String>();
+        if (StringUtils.isEmpty(str))
+        {
+            return list;
+        }
+
+        // 过滤空白字符串
+        if (filterBlank && StringUtils.isBlank(str))
+        {
+            return list;
+        }
+        String[] split = str.split(sep);
+        for (String string : split)
+        {
+            if (filterBlank && StringUtils.isBlank(string))
+            {
+                continue;
+            }
+            if (trim)
+            {
+                string = string.trim();
+            }
+            list.add(string);
+        }
+
+        return list;
+    }
+
+    /**
+     * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
+     *
+     * @param collection 给定的集合
+     * @param array 给定的数组
+     * @return boolean 结果
+     */
+    public static boolean containsAny(Collection<String> collection, String... array)
+    {
+        if (isEmpty(collection) || isEmpty(array))
+        {
+            return false;
+        }
+        else
+        {
+            for (String str : array)
+            {
+                if (collection.contains(str))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
+     *
+     * @param cs 指定字符串
+     * @param searchCharSequences 需要检查的字符串数组
+     * @return 是否包含任意一个字符串
+     */
+    public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences)
+    {
+        if (isEmpty(cs) || isEmpty(searchCharSequences))
+        {
+            return false;
+        }
+        for (CharSequence testStr : searchCharSequences)
+        {
+            if (containsIgnoreCase(cs, testStr))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 驼峰转下划线命名
+     */
+    public static String toUnderScoreCase(String str)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        // 前置字符是否大写
+        boolean preCharIsUpperCase = true;
+        // 当前字符是否大写
+        boolean curreCharIsUpperCase = true;
+        // 下一字符是否大写
+        boolean nexteCharIsUpperCase = true;
+        for (int i = 0; i < str.length(); i++)
+        {
+            char c = str.charAt(i);
+            if (i > 0)
+            {
+                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
+            }
+            else
+            {
+                preCharIsUpperCase = false;
+            }
+
+            curreCharIsUpperCase = Character.isUpperCase(c);
+
+            if (i < (str.length() - 1))
+            {
+                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
+            }
+
+            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase)
+            {
+                sb.append(SEPARATOR);
+            }
+            else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase)
+            {
+                sb.append(SEPARATOR);
+            }
+            sb.append(Character.toLowerCase(c));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * 是否包含字符串
+     * 
+     * @param str 验证字符串
+     * @param strs 字符串组
+     * @return 包含返回true
+     */
+    public static boolean inStringIgnoreCase(String str, String... strs)
+    {
+        if (str != null && strs != null)
+        {
+            for (String s : strs)
+            {
+                if (str.equalsIgnoreCase(trim(s)))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
+     * 
+     * @param name 转换前的下划线大写方式命名的字符串
+     * @return 转换后的驼峰式命名的字符串
+     */
+    public static String convertToCamelCase(String name)
+    {
+        StringBuilder result = new StringBuilder();
+        // 快速检查
+        if (name == null || name.isEmpty())
+        {
+            // 没必要转换
+            return "";
+        }
+        else if (!name.contains("_"))
+        {
+            // 不含下划线,仅将首字母大写
+            return name.substring(0, 1).toUpperCase() + name.substring(1);
+        }
+        // 用下划线将原始字符串分割
+        String[] camels = name.split("_");
+        for (String camel : camels)
+        {
+            // 跳过原始字符串中开头、结尾的下换线或双重下划线
+            if (camel.isEmpty())
+            {
+                continue;
+            }
+            // 首字母大写
+            result.append(camel.substring(0, 1).toUpperCase());
+            result.append(camel.substring(1).toLowerCase());
+        }
+        return result.toString();
+    }
+
+    /**
+     * 驼峰式命名法
+     * 例如:user_name->userName
+     */
+    public static String toCamelCase(String s)
+    {
+        if (s == null)
+        {
+            return null;
+        }
+        if (s.indexOf(SEPARATOR) == -1)
+        {
+            return s;
+        }
+        s = s.toLowerCase();
+        StringBuilder sb = new StringBuilder(s.length());
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++)
+        {
+            char c = s.charAt(i);
+
+            if (c == SEPARATOR)
+            {
+                upperCase = true;
+            }
+            else if (upperCase)
+            {
+                sb.append(Character.toUpperCase(c));
+                upperCase = false;
+            }
+            else
+            {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
+     * 
+     * @param str 指定字符串
+     * @param strs 需要检查的字符串数组
+     * @return 是否匹配
+     */
+    public static boolean matches(String str, List<String> strs)
+    {
+        if (isEmpty(str) || isEmpty(strs))
+        {
+            return false;
+        }
+        for (String pattern : strs)
+        {
+            if (isMatch(pattern, str))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断url是否与规则配置: 
+     * ? 表示单个字符; 
+     * * 表示一层路径内的任意字符串,不可跨层级; 
+     * ** 表示任意层路径;
+     * 
+     * @param pattern 匹配规则
+     * @param url 需要匹配的url
+     * @return
+     */
+    public static boolean isMatch(String pattern, String url)
+    {
+        AntPathMatcher matcher = new AntPathMatcher();
+        return matcher.match(pattern, url);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T cast(Object obj)
+    {
+        return (T) obj;
+    }
+
+    /**
+     * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
+     * 
+     * @param num 数字对象
+     * @param size 字符串指定长度
+     * @return 返回数字的字符串格式,该字符串为指定长度。
+     */
+    public static final String padl(final Number num, final int size)
+    {
+        return padl(num.toString(), size, '0');
+    }
+
+    /**
+     * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
+     * 
+     * @param s 原始字符串
+     * @param size 字符串指定长度
+     * @param c 用于补齐的字符
+     * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
+     */
+    public static final 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)
+            {
+                for (int i = size - len; i > 0; i--)
+                {
+                    sb.append(c);
+                }
+                sb.append(s);
+            }
+            else
+            {
+                return s.substring(len - size, len);
+            }
+        }
+        else
+        {
+            for (int i = size; i > 0; i--)
+            {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java
new file mode 100644
index 0000000..71fe6d5
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java
@@ -0,0 +1,99 @@
+package com.ruoyi.common.utils;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 线程相关工具类.
+ * 
+ * @author ruoyi
+ */
+public class Threads
+{
+    private static final Logger logger = LoggerFactory.getLogger(Threads.class);
+
+    /**
+     * sleep等待,单位为毫秒
+     */
+    public static void sleep(long milliseconds)
+    {
+        try
+        {
+            Thread.sleep(milliseconds);
+        }
+        catch (InterruptedException e)
+        {
+            return;
+        }
+    }
+
+    /**
+     * 停止线程池
+     * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
+     * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
+     * 如果仍然超時,則強制退出.
+     * 另对在shutdown时线程本身被调用中断做了处理.
+     */
+    public static void shutdownAndAwaitTermination(ExecutorService pool)
+    {
+        if (pool != null && !pool.isShutdown())
+        {
+            pool.shutdown();
+            try
+            {
+                if (!pool.awaitTermination(120, TimeUnit.SECONDS))
+                {
+                    pool.shutdownNow();
+                    if (!pool.awaitTermination(120, TimeUnit.SECONDS))
+                    {
+                        logger.info("Pool did not terminate");
+                    }
+                }
+            }
+            catch (InterruptedException ie)
+            {
+                pool.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * 打印线程异常信息
+     */
+    public static void printException(Runnable r, Throwable t)
+    {
+        if (t == null && r instanceof Future<?>)
+        {
+            try
+            {
+                Future<?> future = (Future<?>) r;
+                if (future.isDone())
+                {
+                    future.get();
+                }
+            }
+            catch (CancellationException ce)
+            {
+                t = ce;
+            }
+            catch (ExecutionException ee)
+            {
+                t = ee.getCause();
+            }
+            catch (InterruptedException ie)
+            {
+                Thread.currentThread().interrupt();
+            }
+        }
+        if (t != null)
+        {
+            logger.error(t.getMessage(), t);
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java
new file mode 100644
index 0000000..4463662
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java
@@ -0,0 +1,110 @@
+package com.ruoyi.common.utils.bean;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Bean 工具类
+ * 
+ * @author ruoyi
+ */
+public class BeanUtils extends org.springframework.beans.BeanUtils
+{
+    /** Bean方法名中属性名开始的下标 */
+    private static final int BEAN_METHOD_PROP_INDEX = 3;
+
+    /** * 匹配getter方法的正则表达式 */
+    private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)");
+
+    /** * 匹配setter方法的正则表达式 */
+    private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)");
+
+    /**
+     * Bean属性复制工具方法。
+     * 
+     * @param dest 目标对象
+     * @param src 源对象
+     */
+    public static void copyBeanProp(Object dest, Object src)
+    {
+        try
+        {
+            copyProperties(src, dest);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取对象的setter方法。
+     * 
+     * @param obj 对象
+     * @return 对象的setter方法列表
+     */
+    public static List<Method> getSetterMethods(Object obj)
+    {
+        // setter方法列表
+        List<Method> setterMethods = new ArrayList<Method>();
+
+        // 获取所有方法
+        Method[] methods = obj.getClass().getMethods();
+
+        // 查找setter方法
+
+        for (Method method : methods)
+        {
+            Matcher m = SET_PATTERN.matcher(method.getName());
+            if (m.matches() && (method.getParameterTypes().length == 1))
+            {
+                setterMethods.add(method);
+            }
+        }
+        // 返回setter方法列表
+        return setterMethods;
+    }
+
+    /**
+     * 获取对象的getter方法。
+     * 
+     * @param obj 对象
+     * @return 对象的getter方法列表
+     */
+
+    public static List<Method> getGetterMethods(Object obj)
+    {
+        // getter方法列表
+        List<Method> getterMethods = new ArrayList<Method>();
+        // 获取所有方法
+        Method[] methods = obj.getClass().getMethods();
+        // 查找getter方法
+        for (Method method : methods)
+        {
+            Matcher m = GET_PATTERN.matcher(method.getName());
+            if (m.matches() && (method.getParameterTypes().length == 0))
+            {
+                getterMethods.add(method);
+            }
+        }
+        // 返回getter方法列表
+        return getterMethods;
+    }
+
+    /**
+     * 检查Bean方法名中的属性名是否相等。<br>
+     * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。
+     * 
+     * @param m1 方法名1
+     * @param m2 方法名2
+     * @return 属性名一样返回true,否则返回false
+     */
+
+    public static boolean isMethodPropEquals(String m1, String m2)
+    {
+        return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX));
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java
new file mode 100644
index 0000000..898aca5
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.utils.bean;
+
+import java.util.Set;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validator;
+
+/**
+ * bean对象属性验证
+ * 
+ * @author ruoyi
+ */
+public class BeanValidators
+{
+    public static void validateWithException(Validator validator, Object object, Class<?>... groups)
+            throws ConstraintViolationException
+    {
+        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
+        if (!constraintViolations.isEmpty())
+        {
+            throw new ConstraintViolationException(constraintViolations);
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java
new file mode 100644
index 0000000..68130b9
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java
@@ -0,0 +1,76 @@
+package com.ruoyi.common.utils.file;
+
+import java.io.File;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 文件类型工具类
+ *
+ * @author ruoyi
+ */
+public class FileTypeUtils
+{
+    /**
+     * 获取文件类型
+     * <p>
+     * 例如: ruoyi.txt, 返回: txt
+     * 
+     * @param file 文件名
+     * @return 后缀(不含".")
+     */
+    public static String getFileType(File file)
+    {
+        if (null == file)
+        {
+            return StringUtils.EMPTY;
+        }
+        return getFileType(file.getName());
+    }
+
+    /**
+     * 获取文件类型
+     * <p>
+     * 例如: ruoyi.txt, 返回: txt
+     *
+     * @param fileName 文件名
+     * @return 后缀(不含".")
+     */
+    public static String getFileType(String fileName)
+    {
+        int separatorIndex = fileName.lastIndexOf(".");
+        if (separatorIndex < 0)
+        {
+            return "";
+        }
+        return fileName.substring(separatorIndex + 1).toLowerCase();
+    }
+
+    /**
+     * 获取文件类型
+     * 
+     * @param photoByte 文件字节码
+     * @return 后缀(不含".")
+     */
+    public static String getFileExtendName(byte[] photoByte)
+    {
+        String strFileExtendName = "JPG";
+        if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
+                && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
+        {
+            strFileExtendName = "GIF";
+        }
+        else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
+        {
+            strFileExtendName = "JPG";
+        }
+        else if ((photoByte[0] == 66) && (photoByte[1] == 77))
+        {
+            strFileExtendName = "BMP";
+        }
+        else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
+        {
+            strFileExtendName = "PNG";
+        }
+        return strFileExtendName;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java
new file mode 100644
index 0000000..d5455c4
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java
@@ -0,0 +1,232 @@
+package com.ruoyi.common.utils.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Objects;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException;
+import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
+import com.ruoyi.common.exception.file.InvalidExtensionException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.uuid.Seq;
+
+/**
+ * 文件上传工具类
+ *
+ * @author ruoyi
+ */
+public class FileUploadUtils
+{
+    /**
+     * 默认大小 50M
+     */
+    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
+
+    /**
+     * 默认的文件名最大长度 100
+     */
+    public static final int DEFAULT_FILE_NAME_LENGTH = 100;
+
+    /**
+     * 默认上传的地址
+     */
+    private static String defaultBaseDir = RuoYiConfig.getProfile();
+
+    public static void setDefaultBaseDir(String defaultBaseDir)
+    {
+        FileUploadUtils.defaultBaseDir = defaultBaseDir;
+    }
+
+    public static String getDefaultBaseDir()
+    {
+        return defaultBaseDir;
+    }
+
+    /**
+     * 以默认配置进行文件上传
+     *
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws Exception
+     */
+    public static final String upload(MultipartFile file) throws IOException
+    {
+        try
+        {
+            return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 根据文件路径上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws IOException
+     */
+    public static final String upload(String baseDir, MultipartFile file) throws IOException
+    {
+        try
+        {
+            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @param allowedExtension 上传文件类型
+     * @return 返回上传成功的文件名
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws FileNameLengthLimitExceededException 文件名太长
+     * @throws IOException 比如读写文件出错时
+     * @throws InvalidExtensionException 文件校验异常
+     */
+    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+            InvalidExtensionException
+    {
+        int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
+        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
+        {
+            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+        }
+
+        assertAllowed(file, allowedExtension);
+
+        String fileName = extractFilename(file);
+
+        String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
+        file.transferTo(Paths.get(absPath));
+        return getPathFileName(baseDir, fileName);
+    }
+
+    /**
+     * 编码文件名
+     */
+    public static final String extractFilename(MultipartFile file)
+    {
+        return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
+                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
+    }
+
+    public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
+    {
+        File desc = new File(uploadDir + File.separator + fileName);
+
+        if (!desc.exists())
+        {
+            if (!desc.getParentFile().exists())
+            {
+                desc.getParentFile().mkdirs();
+            }
+        }
+        return desc;
+    }
+
+    public static final String getPathFileName(String uploadDir, String fileName) throws IOException
+    {
+        int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
+        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
+        return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @return
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException
+     */
+    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, InvalidExtensionException
+    {
+        long size = file.getSize();
+        if (size > DEFAULT_MAX_SIZE)
+        {
+            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+        }
+
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
+        {
+            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else
+            {
+                throw new InvalidExtensionException(allowedExtension, extension, fileName);
+            }
+        }
+    }
+
+    /**
+     * 判断MIME类型是否是允许的MIME类型
+     *
+     * @param extension
+     * @param allowedExtension
+     * @return
+     */
+    public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
+    {
+        for (String str : allowedExtension)
+        {
+            if (str.equalsIgnoreCase(extension))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取文件名的后缀
+     *
+     * @param file 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtension(MultipartFile file)
+    {
+        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+        if (StringUtils.isEmpty(extension))
+        {
+            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
+        }
+        return extension;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
new file mode 100644
index 0000000..2062b75
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
@@ -0,0 +1,291 @@
+package com.ruoyi.common.utils.file;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.uuid.IdUtils;
+import org.apache.commons.io.FilenameUtils;
+
+/**
+ * 文件处理工具类
+ * 
+ * @author ruoyi
+ */
+public class FileUtils
+{
+    public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
+
+    /**
+     * 输出指定文件的byte数组
+     * 
+     * @param filePath 文件路径
+     * @param os 输出流
+     * @return
+     */
+    public static void writeBytes(String filePath, OutputStream os) throws IOException
+    {
+        FileInputStream fis = null;
+        try
+        {
+            File file = new File(filePath);
+            if (!file.exists())
+            {
+                throw new FileNotFoundException(filePath);
+            }
+            fis = new FileInputStream(file);
+            byte[] b = new byte[1024];
+            int length;
+            while ((length = fis.read(b)) > 0)
+            {
+                os.write(b, 0, length);
+            }
+        }
+        catch (IOException e)
+        {
+            throw e;
+        }
+        finally
+        {
+            IOUtils.close(os);
+            IOUtils.close(fis);
+        }
+    }
+
+    /**
+     * 写数据到文件中
+     *
+     * @param data 数据
+     * @return 目标文件
+     * @throws IOException IO异常
+     */
+    public static String writeImportBytes(byte[] data) throws IOException
+    {
+        return writeBytes(data, RuoYiConfig.getImportPath());
+    }
+
+    /**
+     * 写数据到文件中
+     *
+     * @param data 数据
+     * @param uploadDir 目标文件
+     * @return 目标文件
+     * @throws IOException IO异常
+     */
+    public static String writeBytes(byte[] data, String uploadDir) throws IOException
+    {
+        FileOutputStream fos = null;
+        String pathName = "";
+        try
+        {
+            String extension = getFileExtendName(data);
+            pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
+            File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
+            fos = new FileOutputStream(file);
+            fos.write(data);
+        }
+        finally
+        {
+            IOUtils.close(fos);
+        }
+        return FileUploadUtils.getPathFileName(uploadDir, pathName);
+    }
+
+    /**
+     * 删除文件
+     * 
+     * @param filePath 文件
+     * @return
+     */
+    public static boolean deleteFile(String filePath)
+    {
+        boolean flag = false;
+        File file = new File(filePath);
+        // 路径为文件且不为空则进行删除
+        if (file.isFile() && file.exists())
+        {
+            flag = file.delete();
+        }
+        return flag;
+    }
+
+    /**
+     * 文件名称验证
+     * 
+     * @param filename 文件名称
+     * @return true 正常 false 非法
+     */
+    public static boolean isValidFilename(String filename)
+    {
+        return filename.matches(FILENAME_PATTERN);
+    }
+
+    /**
+     * 检查文件是否可下载
+     * 
+     * @param resource 需要下载的文件
+     * @return true 正常 false 非法
+     */
+    public static boolean checkAllowDownload(String resource)
+    {
+        // 禁止目录上跳级别
+        if (StringUtils.contains(resource, ".."))
+        {
+            return false;
+        }
+
+        // 检查允许下载的文件规则
+        if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
+        {
+            return true;
+        }
+
+        // 不在允许下载的文件规则
+        return false;
+    }
+
+    /**
+     * 下载文件名重新编码
+     * 
+     * @param request 请求对象
+     * @param fileName 文件名
+     * @return 编码后的文件名
+     */
+    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
+    {
+        final String agent = request.getHeader("USER-AGENT");
+        String filename = fileName;
+        if (agent.contains("MSIE"))
+        {
+            // IE浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+            filename = filename.replace("+", " ");
+        }
+        else if (agent.contains("Firefox"))
+        {
+            // 火狐浏览器
+            filename = new String(fileName.getBytes(), "ISO8859-1");
+        }
+        else if (agent.contains("Chrome"))
+        {
+            // google浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+        }
+        else
+        {
+            // 其它浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+        }
+        return filename;
+    }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param response 响应对象
+     * @param realFileName 真实文件名
+     */
+    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
+    {
+        String percentEncodedFileName = percentEncode(realFileName);
+
+        StringBuilder contentDispositionValue = new StringBuilder();
+        contentDispositionValue.append("attachment; filename=")
+                .append(percentEncodedFileName)
+                .append(";")
+                .append("filename*=")
+                .append("utf-8''")
+                .append(percentEncodedFileName);
+
+        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
+        response.setHeader("Content-disposition", contentDispositionValue.toString());
+        response.setHeader("download-filename", percentEncodedFileName);
+    }
+
+    /**
+     * 百分号编码工具方法
+     *
+     * @param s 需要百分号编码的字符串
+     * @return 百分号编码后的字符串
+     */
+    public static String percentEncode(String s) throws UnsupportedEncodingException
+    {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
+        return encode.replaceAll("\\+", "%20");
+    }
+
+    /**
+     * 获取图像后缀
+     * 
+     * @param photoByte 图像数据
+     * @return 后缀名
+     */
+    public static String getFileExtendName(byte[] photoByte)
+    {
+        String strFileExtendName = "jpg";
+        if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
+                && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
+        {
+            strFileExtendName = "gif";
+        }
+        else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
+        {
+            strFileExtendName = "jpg";
+        }
+        else if ((photoByte[0] == 66) && (photoByte[1] == 77))
+        {
+            strFileExtendName = "bmp";
+        }
+        else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
+        {
+            strFileExtendName = "png";
+        }
+        return strFileExtendName;
+    }
+
+    /**
+     * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png
+     * 
+     * @param fileName 路径名称
+     * @return 没有文件路径的名称
+     */
+    public static String getName(String fileName)
+    {
+        if (fileName == null)
+        {
+            return null;
+        }
+        int lastUnixPos = fileName.lastIndexOf('/');
+        int lastWindowsPos = fileName.lastIndexOf('\\');
+        int index = Math.max(lastUnixPos, lastWindowsPos);
+        return fileName.substring(index + 1);
+    }
+
+    /**
+     * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi
+     * 
+     * @param fileName 路径名称
+     * @return 没有文件路径和后缀的名称
+     */
+    public static String getNameNotSuffix(String fileName)
+    {
+        if (fileName == null)
+        {
+            return null;
+        }
+        String baseName = FilenameUtils.getBaseName(fileName);
+        return baseName;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java
new file mode 100644
index 0000000..432dfda
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java
@@ -0,0 +1,98 @@
+package com.ruoyi.common.utils.file;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import org.apache.poi.util.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 图片处理工具类
+ *
+ * @author ruoyi
+ */
+public class ImageUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
+
+    public static byte[] getImage(String imagePath)
+    {
+        InputStream is = getFile(imagePath);
+        try
+        {
+            return IOUtils.toByteArray(is);
+        }
+        catch (Exception e)
+        {
+            log.error("图片加载异常 {}", e);
+            return null;
+        }
+        finally
+        {
+            IOUtils.closeQuietly(is);
+        }
+    }
+
+    public static InputStream getFile(String imagePath)
+    {
+        try
+        {
+            byte[] result = readFile(imagePath);
+            result = Arrays.copyOf(result, result.length);
+            return new ByteArrayInputStream(result);
+        }
+        catch (Exception e)
+        {
+            log.error("获取图片异常 {}", e);
+        }
+        return null;
+    }
+
+    /**
+     * 读取文件为字节数据
+     * 
+     * @param url 地址
+     * @return 字节数据
+     */
+    public static byte[] readFile(String url)
+    {
+        InputStream in = null;
+        try
+        {
+            if (url.startsWith("http"))
+            {
+                // 网络地址
+                URL urlObj = new URL(url);
+                URLConnection urlConnection = urlObj.openConnection();
+                urlConnection.setConnectTimeout(30 * 1000);
+                urlConnection.setReadTimeout(60 * 1000);
+                urlConnection.setDoInput(true);
+                in = urlConnection.getInputStream();
+            }
+            else
+            {
+                // 本机地址
+                String localPath = RuoYiConfig.getProfile();
+                String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX);
+                in = new FileInputStream(downloadPath);
+            }
+            return IOUtils.toByteArray(in);
+        }
+        catch (Exception e)
+        {
+            log.error("获取文件路径异常 {}", e);
+            return null;
+        }
+        finally
+        {
+            IOUtils.closeQuietly(in);
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java
new file mode 100644
index 0000000..f968f1a
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java
@@ -0,0 +1,59 @@
+package com.ruoyi.common.utils.file;
+
+/**
+ * 媒体类型工具类
+ * 
+ * @author ruoyi
+ */
+public class MimeTypeUtils
+{
+    public static final String IMAGE_PNG = "image/png";
+
+    public static final String IMAGE_JPG = "image/jpg";
+
+    public static final String IMAGE_JPEG = "image/jpeg";
+
+    public static final String IMAGE_BMP = "image/bmp";
+
+    public static final String IMAGE_GIF = "image/gif";
+    
+    public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
+
+    public static final String[] FLASH_EXTENSION = { "swf", "flv" };
+
+    public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+            "asf", "rm", "rmvb" };
+
+    public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };
+
+    public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+            // 图片
+            "bmp", "gif", "jpg", "jpeg", "png",
+            // word excel powerpoint
+            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+            // 压缩文件
+            "rar", "zip", "gz", "bz2",
+            // 视频格式
+            "mp4", "avi", "rmvb",
+            // pdf
+            "pdf" };
+
+    public static String getExtension(String prefix)
+    {
+        switch (prefix)
+        {
+            case IMAGE_PNG:
+                return "png";
+            case IMAGE_JPG:
+                return "jpg";
+            case IMAGE_JPEG:
+                return "jpeg";
+            case IMAGE_BMP:
+                return "bmp";
+            case IMAGE_GIF:
+                return "gif";
+            default:
+                return "";
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java
new file mode 100644
index 0000000..f52e83e
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java
@@ -0,0 +1,167 @@
+package com.ruoyi.common.utils.html;
+
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 转义和反转义工具类
+ * 
+ * @author ruoyi
+ */
+public class EscapeUtil
+{
+    public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";
+
+    private static final char[][] TEXT = new char[64][];
+
+    static
+    {
+        for (int i = 0; i < 64; i++)
+        {
+            TEXT[i] = new char[] { (char) i };
+        }
+
+        // special HTML characters
+        TEXT['\''] = "&#039;".toCharArray(); // 单引号
+        TEXT['"'] = "&#34;".toCharArray(); // 双引号
+        TEXT['&'] = "&#38;".toCharArray(); // &符
+        TEXT['<'] = "&#60;".toCharArray(); // 小于号
+        TEXT['>'] = "&#62;".toCharArray(); // 大于号
+    }
+
+    /**
+     * 转义文本中的HTML字符为安全的字符
+     * 
+     * @param text 被转义的文本
+     * @return 转义后的文本
+     */
+    public static String escape(String text)
+    {
+        return encode(text);
+    }
+
+    /**
+     * 还原被转义的HTML特殊字符
+     * 
+     * @param content 包含转义符的HTML内容
+     * @return 转换后的字符串
+     */
+    public static String unescape(String content)
+    {
+        return decode(content);
+    }
+
+    /**
+     * 清除所有HTML标签,但是不删除标签内的内容
+     * 
+     * @param content 文本
+     * @return 清除标签后的文本
+     */
+    public static String clean(String content)
+    {
+        return new HTMLFilter().filter(content);
+    }
+
+    /**
+     * Escape编码
+     * 
+     * @param text 被编码的文本
+     * @return 编码后的字符
+     */
+    private static String encode(String text)
+    {
+        if (StringUtils.isEmpty(text))
+        {
+            return StringUtils.EMPTY;
+        }
+
+        final StringBuilder tmp = new StringBuilder(text.length() * 6);
+        char c;
+        for (int i = 0; i < text.length(); i++)
+        {
+            c = text.charAt(i);
+            if (c < 256)
+            {
+                tmp.append("%");
+                if (c < 16)
+                {
+                    tmp.append("0");
+                }
+                tmp.append(Integer.toString(c, 16));
+            }
+            else
+            {
+                tmp.append("%u");
+                if (c <= 0xfff)
+                {
+                    // issue#I49JU8@Gitee
+                    tmp.append("0");
+                }
+                tmp.append(Integer.toString(c, 16));
+            }
+        }
+        return tmp.toString();
+    }
+
+    /**
+     * Escape解码
+     * 
+     * @param content 被转义的内容
+     * @return 解码后的字符串
+     */
+    public static String decode(String content)
+    {
+        if (StringUtils.isEmpty(content))
+        {
+            return content;
+        }
+
+        StringBuilder tmp = new StringBuilder(content.length());
+        int lastPos = 0, pos = 0;
+        char ch;
+        while (lastPos < content.length())
+        {
+            pos = content.indexOf("%", lastPos);
+            if (pos == lastPos)
+            {
+                if (content.charAt(pos + 1) == 'u')
+                {
+                    ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16);
+                    tmp.append(ch);
+                    lastPos = pos + 6;
+                }
+                else
+                {
+                    ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16);
+                    tmp.append(ch);
+                    lastPos = pos + 3;
+                }
+            }
+            else
+            {
+                if (pos == -1)
+                {
+                    tmp.append(content.substring(lastPos));
+                    lastPos = content.length();
+                }
+                else
+                {
+                    tmp.append(content.substring(lastPos, pos));
+                    lastPos = pos;
+                }
+            }
+        }
+        return tmp.toString();
+    }
+
+    public static void main(String[] args)
+    {
+        String html = "<script>alert(1);</script>";
+        String escape = EscapeUtil.escape(html);
+        // String html = "<scr<script>ipt>alert(\"XSS\")</scr<script>ipt>";
+        // String html = "<123";
+        // String html = "123>";
+        System.out.println("clean: " + EscapeUtil.clean(html));
+        System.out.println("escape: " + escape);
+        System.out.println("unescape: " + EscapeUtil.unescape(escape));
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java
new file mode 100644
index 0000000..ebff3fd
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java
@@ -0,0 +1,570 @@
+package com.ruoyi.common.utils.html;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * HTML过滤器,用于去除XSS漏洞隐患。
+ *
+ * @author ruoyi
+ */
+public final class HTMLFilter
+{
+    /**
+     * regex flag union representing /si modifiers in php
+     **/
+    private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
+    private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
+    private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
+    private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
+    private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
+    private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
+    private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
+    private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
+    private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
+    private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
+    private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
+    private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
+    private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
+    private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
+    private static final Pattern P_END_ARROW = Pattern.compile("^>");
+    private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
+    private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
+    private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
+    private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
+    private static final Pattern P_AMP = Pattern.compile("&");
+    private static final Pattern P_QUOTE = Pattern.compile("\"");
+    private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
+    private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
+    private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
+
+    // @xxx could grow large... maybe use sesat's ReferenceMap
+    private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
+    private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>();
+
+    /**
+     * set of allowed html elements, along with allowed attributes for each element
+     **/
+    private final Map<String, List<String>> vAllowed;
+    /**
+     * counts of open tags for each (allowable) html element
+     **/
+    private final Map<String, Integer> vTagCounts = new HashMap<>();
+
+    /**
+     * html elements which must always be self-closing (e.g. "<img />")
+     **/
+    private final String[] vSelfClosingTags;
+    /**
+     * html elements which must always have separate opening and closing tags (e.g. "<b></b>")
+     **/
+    private final String[] vNeedClosingTags;
+    /**
+     * set of disallowed html elements
+     **/
+    private final String[] vDisallowed;
+    /**
+     * attributes which should be checked for valid protocols
+     **/
+    private final String[] vProtocolAtts;
+    /**
+     * allowed protocols
+     **/
+    private final String[] vAllowedProtocols;
+    /**
+     * tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />")
+     **/
+    private final String[] vRemoveBlanks;
+    /**
+     * entities allowed within html markup
+     **/
+    private final String[] vAllowedEntities;
+    /**
+     * flag determining whether comments are allowed in input String.
+     */
+    private final boolean stripComment;
+    private final boolean encodeQuotes;
+    /**
+     * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "<b text </b>"
+     * becomes "<b> text </b>"). If set to false, unbalanced angle brackets will be html escaped.
+     */
+    private final boolean alwaysMakeTags;
+
+    /**
+     * Default constructor.
+     */
+    public HTMLFilter()
+    {
+        vAllowed = new HashMap<>();
+
+        final ArrayList<String> a_atts = new ArrayList<>();
+        a_atts.add("href");
+        a_atts.add("target");
+        vAllowed.put("a", a_atts);
+
+        final ArrayList<String> img_atts = new ArrayList<>();
+        img_atts.add("src");
+        img_atts.add("width");
+        img_atts.add("height");
+        img_atts.add("alt");
+        vAllowed.put("img", img_atts);
+
+        final ArrayList<String> no_atts = new ArrayList<>();
+        vAllowed.put("b", no_atts);
+        vAllowed.put("strong", no_atts);
+        vAllowed.put("i", no_atts);
+        vAllowed.put("em", no_atts);
+
+        vSelfClosingTags = new String[] { "img" };
+        vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" };
+        vDisallowed = new String[] {};
+        vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp.
+        vProtocolAtts = new String[] { "src", "href" };
+        vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" };
+        vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" };
+        stripComment = true;
+        encodeQuotes = true;
+        alwaysMakeTags = false;
+    }
+
+    /**
+     * Map-parameter configurable constructor.
+     *
+     * @param conf map containing configuration. keys match field names.
+     */
+    @SuppressWarnings("unchecked")
+    public HTMLFilter(final Map<String, Object> conf)
+    {
+
+        assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
+        assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
+        assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
+        assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
+        assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
+        assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
+        assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
+        assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
+
+        vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
+        vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
+        vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
+        vDisallowed = (String[]) conf.get("vDisallowed");
+        vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
+        vProtocolAtts = (String[]) conf.get("vProtocolAtts");
+        vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
+        vAllowedEntities = (String[]) conf.get("vAllowedEntities");
+        stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
+        encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
+        alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
+    }
+
+    private void reset()
+    {
+        vTagCounts.clear();
+    }
+
+    // ---------------------------------------------------------------
+    // my versions of some PHP library functions
+    public static String chr(final int decimal)
+    {
+        return String.valueOf((char) decimal);
+    }
+
+    public static String htmlSpecialChars(final String s)
+    {
+        String result = s;
+        result = regexReplace(P_AMP, "&amp;", result);
+        result = regexReplace(P_QUOTE, "&quot;", result);
+        result = regexReplace(P_LEFT_ARROW, "&lt;", result);
+        result = regexReplace(P_RIGHT_ARROW, "&gt;", result);
+        return result;
+    }
+
+    // ---------------------------------------------------------------
+
+    /**
+     * given a user submitted input String, filter out any invalid or restricted html.
+     *
+     * @param input text (i.e. submitted by a user) than may contain html
+     * @return "clean" version of input, with only valid, whitelisted html elements allowed
+     */
+    public String filter(final String input)
+    {
+        reset();
+        String s = input;
+
+        s = escapeComments(s);
+
+        s = balanceHTML(s);
+
+        s = checkTags(s);
+
+        s = processRemoveBlanks(s);
+
+        // s = validateEntities(s);
+
+        return s;
+    }
+
+    public boolean isAlwaysMakeTags()
+    {
+        return alwaysMakeTags;
+    }
+
+    public boolean isStripComments()
+    {
+        return stripComment;
+    }
+
+    private String escapeComments(final String s)
+    {
+        final Matcher m = P_COMMENTS.matcher(s);
+        final StringBuffer buf = new StringBuffer();
+        if (m.find())
+        {
+            final String match = m.group(1); // (.*?)
+            m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
+        }
+        m.appendTail(buf);
+
+        return buf.toString();
+    }
+
+    private String balanceHTML(String s)
+    {
+        if (alwaysMakeTags)
+        {
+            //
+            // try and form html
+            //
+            s = regexReplace(P_END_ARROW, "", s);
+            // 不追加结束标签
+            s = regexReplace(P_BODY_TO_END, "<$1>", s);
+            s = regexReplace(P_XML_CONTENT, "$1<$2", s);
+
+        }
+        else
+        {
+            //
+            // escape stray brackets
+            //
+            s = regexReplace(P_STRAY_LEFT_ARROW, "&lt;$1", s);
+            s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);
+
+            //
+            // the last regexp causes '<>' entities to appear
+            // (we need to do a lookahead assertion so that the last bracket can
+            // be used in the next pass of the regexp)
+            //
+            s = regexReplace(P_BOTH_ARROWS, "", s);
+        }
+
+        return s;
+    }
+
+    private String checkTags(String s)
+    {
+        Matcher m = P_TAGS.matcher(s);
+
+        final StringBuffer buf = new StringBuffer();
+        while (m.find())
+        {
+            String replaceStr = m.group(1);
+            replaceStr = processTag(replaceStr);
+            m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
+        }
+        m.appendTail(buf);
+
+        // these get tallied in processTag
+        // (remember to reset before subsequent calls to filter method)
+        final StringBuilder sBuilder = new StringBuilder(buf.toString());
+        for (String key : vTagCounts.keySet())
+        {
+            for (int ii = 0; ii < vTagCounts.get(key); ii++)
+            {
+                sBuilder.append("</").append(key).append(">");
+            }
+        }
+        s = sBuilder.toString();
+
+        return s;
+    }
+
+    private String processRemoveBlanks(final String s)
+    {
+        String result = s;
+        for (String tag : vRemoveBlanks)
+        {
+            if (!P_REMOVE_PAIR_BLANKS.containsKey(tag))
+            {
+                P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
+            }
+            result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
+            if (!P_REMOVE_SELF_BLANKS.containsKey(tag))
+            {
+                P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
+            }
+            result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
+        }
+
+        return result;
+    }
+
+    private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s)
+    {
+        Matcher m = regex_pattern.matcher(s);
+        return m.replaceAll(replacement);
+    }
+
+    private String processTag(final String s)
+    {
+        // ending tags
+        Matcher m = P_END_TAG.matcher(s);
+        if (m.find())
+        {
+            final String name = m.group(1).toLowerCase();
+            if (allowed(name))
+            {
+                if (!inArray(name, vSelfClosingTags))
+                {
+                    if (vTagCounts.containsKey(name))
+                    {
+                        vTagCounts.put(name, vTagCounts.get(name) - 1);
+                        return "</" + name + ">";
+                    }
+                }
+            }
+        }
+
+        // starting tags
+        m = P_START_TAG.matcher(s);
+        if (m.find())
+        {
+            final String name = m.group(1).toLowerCase();
+            final String body = m.group(2);
+            String ending = m.group(3);
+
+            // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
+            if (allowed(name))
+            {
+                final StringBuilder params = new StringBuilder();
+
+                final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
+                final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
+                final List<String> paramNames = new ArrayList<>();
+                final List<String> paramValues = new ArrayList<>();
+                while (m2.find())
+                {
+                    paramNames.add(m2.group(1)); // ([a-z0-9]+)
+                    paramValues.add(m2.group(3)); // (.*?)
+                }
+                while (m3.find())
+                {
+                    paramNames.add(m3.group(1)); // ([a-z0-9]+)
+                    paramValues.add(m3.group(3)); // ([^\"\\s']+)
+                }
+
+                String paramName, paramValue;
+                for (int ii = 0; ii < paramNames.size(); ii++)
+                {
+                    paramName = paramNames.get(ii).toLowerCase();
+                    paramValue = paramValues.get(ii);
+
+                    // debug( "paramName='" + paramName + "'" );
+                    // debug( "paramValue='" + paramValue + "'" );
+                    // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );
+
+                    if (allowedAttribute(name, paramName))
+                    {
+                        if (inArray(paramName, vProtocolAtts))
+                        {
+                            paramValue = processParamProtocol(paramValue);
+                        }
+                        params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\"");
+                    }
+                }
+
+                if (inArray(name, vSelfClosingTags))
+                {
+                    ending = " /";
+                }
+
+                if (inArray(name, vNeedClosingTags))
+                {
+                    ending = "";
+                }
+
+                if (ending == null || ending.length() < 1)
+                {
+                    if (vTagCounts.containsKey(name))
+                    {
+                        vTagCounts.put(name, vTagCounts.get(name) + 1);
+                    }
+                    else
+                    {
+                        vTagCounts.put(name, 1);
+                    }
+                }
+                else
+                {
+                    ending = " /";
+                }
+                return "<" + name + params + ending + ">";
+            }
+            else
+            {
+                return "";
+            }
+        }
+
+        // comments
+        m = P_COMMENT.matcher(s);
+        if (!stripComment && m.find())
+        {
+            return "<" + m.group() + ">";
+        }
+
+        return "";
+    }
+
+    private String processParamProtocol(String s)
+    {
+        s = decodeEntities(s);
+        final Matcher m = P_PROTOCOL.matcher(s);
+        if (m.find())
+        {
+            final String protocol = m.group(1);
+            if (!inArray(protocol, vAllowedProtocols))
+            {
+                // bad protocol, turn into local anchor link instead
+                s = "#" + s.substring(protocol.length() + 1);
+                if (s.startsWith("#//"))
+                {
+                    s = "#" + s.substring(3);
+                }
+            }
+        }
+
+        return s;
+    }
+
+    private String decodeEntities(String s)
+    {
+        StringBuffer buf = new StringBuffer();
+
+        Matcher m = P_ENTITY.matcher(s);
+        while (m.find())
+        {
+            final String match = m.group(1);
+            final int decimal = Integer.decode(match).intValue();
+            m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+        }
+        m.appendTail(buf);
+        s = buf.toString();
+
+        buf = new StringBuffer();
+        m = P_ENTITY_UNICODE.matcher(s);
+        while (m.find())
+        {
+            final String match = m.group(1);
+            final int decimal = Integer.valueOf(match, 16).intValue();
+            m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+        }
+        m.appendTail(buf);
+        s = buf.toString();
+
+        buf = new StringBuffer();
+        m = P_ENCODE.matcher(s);
+        while (m.find())
+        {
+            final String match = m.group(1);
+            final int decimal = Integer.valueOf(match, 16).intValue();
+            m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
+        }
+        m.appendTail(buf);
+        s = buf.toString();
+
+        s = validateEntities(s);
+        return s;
+    }
+
+    private String validateEntities(final String s)
+    {
+        StringBuffer buf = new StringBuffer();
+
+        // validate entities throughout the string
+        Matcher m = P_VALID_ENTITIES.matcher(s);
+        while (m.find())
+        {
+            final String one = m.group(1); // ([^&;]*)
+            final String two = m.group(2); // (?=(;|&|$))
+            m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
+        }
+        m.appendTail(buf);
+
+        return encodeQuotes(buf.toString());
+    }
+
+    private String encodeQuotes(final String s)
+    {
+        if (encodeQuotes)
+        {
+            StringBuffer buf = new StringBuffer();
+            Matcher m = P_VALID_QUOTES.matcher(s);
+            while (m.find())
+            {
+                final String one = m.group(1); // (>|^)
+                final String two = m.group(2); // ([^<]+?)
+                final String three = m.group(3); // (<|$)
+                // 不替换双引号为&quot;,防止json格式无效 regexReplace(P_QUOTE, "&quot;", two)
+                m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three));
+            }
+            m.appendTail(buf);
+            return buf.toString();
+        }
+        else
+        {
+            return s;
+        }
+    }
+
+    private String checkEntity(final String preamble, final String term)
+    {
+
+        return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&amp;" + preamble;
+    }
+
+    private boolean isValidEntity(final String entity)
+    {
+        return inArray(entity, vAllowedEntities);
+    }
+
+    private static boolean inArray(final String s, final String[] array)
+    {
+        for (String item : array)
+        {
+            if (item != null && item.equals(s))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean allowed(final String name)
+    {
+        return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
+    }
+
+    private boolean allowedAttribute(final String name, final String paramName)
+    {
+        return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java
new file mode 100644
index 0000000..401f25a
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java
@@ -0,0 +1,55 @@
+package com.ruoyi.common.utils.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import jakarta.servlet.ServletRequest;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 通用http工具封装
+ * 
+ * @author ruoyi
+ */
+public class HttpHelper
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);
+
+    public static String getBodyString(ServletRequest request)
+    {
+        StringBuilder sb = new StringBuilder();
+        BufferedReader reader = null;
+        try (InputStream inputStream = request.getInputStream())
+        {
+            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+            String line = "";
+            while ((line = reader.readLine()) != null)
+            {
+                sb.append(line);
+            }
+        }
+        catch (IOException e)
+        {
+            LOGGER.warn("getBodyString出现问题!");
+        }
+        finally
+        {
+            if (reader != null)
+            {
+                try
+                {
+                    reader.close();
+                }
+                catch (IOException e)
+                {
+                    LOGGER.error(ExceptionUtils.getMessage(e));
+                }
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java
new file mode 100644
index 0000000..ef5eb28
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java
@@ -0,0 +1,274 @@
+package com.ruoyi.common.utils.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 通用http发送方法
+ * 
+ * @author ruoyi
+ */
+public class HttpUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url)
+    {
+        return sendGet(url, StringUtils.EMPTY);
+    }
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url, String param)
+    {
+        return sendGet(url, param, Constants.UTF8);
+    }
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @param contentType 编码类型
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url, String param, String contentType)
+    {
+        StringBuilder result = new StringBuilder();
+        BufferedReader in = null;
+        try
+        {
+            String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
+            log.info("sendGet - {}", urlNameString);
+            URL realUrl = new URL(urlNameString);
+            URLConnection connection = realUrl.openConnection();
+            connection.setRequestProperty("accept", "*/*");
+            connection.setRequestProperty("connection", "Keep-Alive");
+            connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
+            connection.connect();
+            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
+            String line;
+            while ((line = in.readLine()) != null)
+            {
+                result.append(line);
+            }
+            log.info("recv - {}", result);
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
+        }
+        finally
+        {
+            try
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * 向指定 URL 发送POST方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendPost(String url, String param)
+    {
+        PrintWriter out = null;
+        BufferedReader in = null;
+        StringBuilder result = new StringBuilder();
+        try
+        {
+            log.info("sendPost - {}", url);
+            URL realUrl = new URL(url);
+            URLConnection conn = realUrl.openConnection();
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
+            conn.setRequestProperty("Accept-Charset", "utf-8");
+            conn.setRequestProperty("contentType", "utf-8");
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            out = new PrintWriter(conn.getOutputStream());
+            out.print(param);
+            out.flush();
+            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
+            String line;
+            while ((line = in.readLine()) != null)
+            {
+                result.append(line);
+            }
+            log.info("recv - {}", result);
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
+        }
+        finally
+        {
+            try
+            {
+                if (out != null)
+                {
+                    out.close();
+                }
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (IOException ex)
+            {
+                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+        return result.toString();
+    }
+
+    public static String sendSSLPost(String url, String param)
+    {
+        StringBuilder result = new StringBuilder();
+        String urlNameString = url + "?" + param;
+        try
+        {
+            log.info("sendSSLPost - {}", urlNameString);
+            SSLContext sc = SSLContext.getInstance("SSL");
+            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
+            URL console = new URL(urlNameString);
+            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
+            conn.setRequestProperty("Accept-Charset", "utf-8");
+            conn.setRequestProperty("contentType", "utf-8");
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+
+            conn.setSSLSocketFactory(sc.getSocketFactory());
+            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
+            conn.connect();
+            InputStream is = conn.getInputStream();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String ret = "";
+            while ((ret = br.readLine()) != null)
+            {
+                if (ret != null && !"".equals(ret.trim()))
+                {
+                    result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
+                }
+            }
+            log.info("recv - {}", result);
+            conn.disconnect();
+            br.close();
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
+        }
+        return result.toString();
+    }
+
+    private static class TrustAnyTrustManager implements X509TrustManager
+    {
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+        {
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+        {
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers()
+        {
+            return new X509Certificate[] {};
+        }
+    }
+
+    private static class TrustAnyHostnameVerifier implements HostnameVerifier
+    {
+        @Override
+        public boolean verify(String hostname, SSLSession session)
+        {
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java
new file mode 100644
index 0000000..edfe419
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java
@@ -0,0 +1,56 @@
+package com.ruoyi.common.utils.ip;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.http.HttpUtils;
+
+/**
+ * 获取地址类
+ * 
+ * @author ruoyi
+ */
+public class AddressUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
+
+    // IP地址查询
+    public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
+
+    // 未知地址
+    public static final String UNKNOWN = "XX XX";
+
+    public static String getRealAddressByIP(String ip)
+    {
+        // 内网不查询
+        if (IpUtils.internalIp(ip))
+        {
+            return "内网IP";
+        }
+        if (RuoYiConfig.isAddressEnabled())
+        {
+            try
+            {
+                String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK);
+                if (StringUtils.isEmpty(rspStr))
+                {
+                    log.error("获取地理位置异常 {}", ip);
+                    return UNKNOWN;
+                }
+                JSONObject obj = JSON.parseObject(rspStr);
+                String region = obj.getString("pro");
+                String city = obj.getString("city");
+                return String.format("%s %s", region, city);
+            }
+            catch (Exception e)
+            {
+                log.error("获取地理位置异常 {}", ip);
+            }
+        }
+        return UNKNOWN;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
new file mode 100644
index 0000000..a376416
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
@@ -0,0 +1,382 @@
+package com.ruoyi.common.utils.ip;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import jakarta.servlet.http.HttpServletRequest;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 获取IP方法
+ * 
+ * @author ruoyi
+ */
+public class IpUtils
+{
+    public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
+    // 匹配 ip
+    public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
+    public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";
+    // 匹配网段
+    public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";
+
+    /**
+     * 获取客户端IP
+     * 
+     * @return IP地址
+     */
+    public static String getIpAddr()
+    {
+        return getIpAddr(ServletUtils.getRequest());
+    }
+
+    /**
+     * 获取客户端IP
+     * 
+     * @param request 请求对象
+     * @return IP地址
+     */
+    public static String getIpAddr(HttpServletRequest request)
+    {
+        if (request == null)
+        {
+            return "unknown";
+        }
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Real-IP");
+        }
+
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getRemoteAddr();
+        }
+
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
+    }
+
+    /**
+     * 检查是否为内部IP地址
+     * 
+     * @param ip IP地址
+     * @return 结果
+     */
+    public static boolean internalIp(String ip)
+    {
+        byte[] addr = textToNumericFormatV4(ip);
+        return internalIp(addr) || "127.0.0.1".equals(ip);
+    }
+
+    /**
+     * 检查是否为内部IP地址
+     * 
+     * @param addr byte地址
+     * @return 结果
+     */
+    private static boolean internalIp(byte[] addr)
+    {
+        if (StringUtils.isNull(addr) || addr.length < 2)
+        {
+            return true;
+        }
+        final byte b0 = addr[0];
+        final byte b1 = addr[1];
+        // 10.x.x.x/8
+        final byte SECTION_1 = 0x0A;
+        // 172.16.x.x/12
+        final byte SECTION_2 = (byte) 0xAC;
+        final byte SECTION_3 = (byte) 0x10;
+        final byte SECTION_4 = (byte) 0x1F;
+        // 192.168.x.x/16
+        final byte SECTION_5 = (byte) 0xC0;
+        final byte SECTION_6 = (byte) 0xA8;
+        switch (b0)
+        {
+            case SECTION_1:
+                return true;
+            case SECTION_2:
+                if (b1 >= SECTION_3 && b1 <= SECTION_4)
+                {
+                    return true;
+                }
+            case SECTION_5:
+                switch (b1)
+                {
+                    case SECTION_6:
+                        return true;
+                }
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 将IPv4地址转换成字节
+     * 
+     * @param text IPv4地址
+     * @return byte 字节
+     */
+    public static byte[] textToNumericFormatV4(String text)
+    {
+        if (text.length() == 0)
+        {
+            return null;
+        }
+
+        byte[] bytes = new byte[4];
+        String[] elements = text.split("\\.", -1);
+        try
+        {
+            long l;
+            int i;
+            switch (elements.length)
+            {
+                case 1:
+                    l = Long.parseLong(elements[0]);
+                    if ((l < 0L) || (l > 4294967295L))
+                    {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l >> 24 & 0xFF);
+                    bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 2:
+                    l = Integer.parseInt(elements[0]);
+                    if ((l < 0L) || (l > 255L))
+                    {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l & 0xFF);
+                    l = Integer.parseInt(elements[1]);
+                    if ((l < 0L) || (l > 16777215L))
+                    {
+                        return null;
+                    }
+                    bytes[1] = (byte) (int) (l >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 3:
+                    for (i = 0; i < 2; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L))
+                        {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    l = Integer.parseInt(elements[2]);
+                    if ((l < 0L) || (l > 65535L))
+                    {
+                        return null;
+                    }
+                    bytes[2] = (byte) (int) (l >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 4:
+                    for (i = 0; i < 4; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L))
+                        {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    break;
+                default:
+                    return null;
+            }
+        }
+        catch (NumberFormatException e)
+        {
+            return null;
+        }
+        return bytes;
+    }
+
+    /**
+     * 获取IP地址
+     * 
+     * @return 本地IP地址
+     */
+    public static String getHostIp()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostAddress();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "127.0.0.1";
+    }
+
+    /**
+     * 获取主机名
+     * 
+     * @return 本地主机名
+     */
+    public static String getHostName()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostName();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "未知";
+    }
+
+    /**
+     * 从多级反向代理中获得第一个非unknown IP地址
+     *
+     * @param ip 获得的IP地址
+     * @return 第一个非unknown IP地址
+     */
+    public static String getMultistageReverseProxyIp(String ip)
+    {
+        // 多级反向代理检测
+        if (ip != null && ip.indexOf(",") > 0)
+        {
+            final String[] ips = ip.trim().split(",");
+            for (String subIp : ips)
+            {
+                if (false == isUnknown(subIp))
+                {
+                    ip = subIp;
+                    break;
+                }
+            }
+        }
+        return StringUtils.substring(ip, 0, 255);
+    }
+
+    /**
+     * 检测给定字符串是否为未知,多用于检测HTTP请求相关
+     *
+     * @param checkString 被检测的字符串
+     * @return 是否未知
+     */
+    public static boolean isUnknown(String checkString)
+    {
+        return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
+    }
+
+    /**
+     * 是否为IP
+     */
+    public static boolean isIP(String ip)
+    {
+        return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP);
+    }
+
+    /**
+     * 是否为IP,或 *为间隔的通配符地址
+     */
+    public static boolean isIpWildCard(String ip)
+    {
+        return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);
+    }
+
+    /**
+     * 检测参数是否在ip通配符里
+     */
+    public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip)
+    {
+        String[] s1 = ipWildCard.split("\\.");
+        String[] s2 = ip.split("\\.");
+        boolean isMatchedSeg = true;
+        for (int i = 0; i < s1.length && !s1[i].equals("*"); i++)
+        {
+            if (!s1[i].equals(s2[i]))
+            {
+                isMatchedSeg = false;
+                break;
+            }
+        }
+        return isMatchedSeg;
+    }
+
+    /**
+     * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
+     */
+    public static boolean isIPSegment(String ipSeg)
+    {
+        return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);
+    }
+
+    /**
+     * 判断ip是否在指定网段中
+     */
+    public static boolean ipIsInNetNoCheck(String iparea, String ip)
+    {
+        int idx = iparea.indexOf('-');
+        String[] sips = iparea.substring(0, idx).split("\\.");
+        String[] sipe = iparea.substring(idx + 1).split("\\.");
+        String[] sipt = ip.split("\\.");
+        long ips = 0L, ipe = 0L, ipt = 0L;
+        for (int i = 0; i < 4; ++i)
+        {
+            ips = ips << 8 | Integer.parseInt(sips[i]);
+            ipe = ipe << 8 | Integer.parseInt(sipe[i]);
+            ipt = ipt << 8 | Integer.parseInt(sipt[i]);
+        }
+        if (ips > ipe)
+        {
+            long t = ips;
+            ips = ipe;
+            ipe = t;
+        }
+        return ips <= ipt && ipt <= ipe;
+    }
+
+    /**
+     * 校验ip是否符合过滤串规则
+     * 
+     * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
+     * @param ip 校验IP地址
+     * @return boolean 结果
+     */
+    public static boolean isMatchedIp(String filter, String ip)
+    {
+        if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip))
+        {
+            return false;
+        }
+        String[] ips = filter.split(";");
+        for (String iStr : ips)
+        {
+            if (isIP(iStr) && iStr.equals(ip))
+            {
+                return true;
+            }
+            else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip))
+            {
+                return true;
+            }
+            else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java
new file mode 100644
index 0000000..ccab288
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.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, Cell cell, Workbook wb);
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
new file mode 100644
index 0000000..68652f2
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -0,0 +1,1812 @@
+package com.ruoyi.common.utils.poi;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.RegExUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
+import org.apache.poi.hssf.usermodel.HSSFPicture;
+import org.apache.poi.hssf.usermodel.HSSFPictureData;
+import org.apache.poi.hssf.usermodel.HSSFShape;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.ClientAnchor;
+import org.apache.poi.ss.usermodel.DataFormat;
+import org.apache.poi.ss.usermodel.DataValidation;
+import org.apache.poi.ss.usermodel.DataValidationConstraint;
+import org.apache.poi.ss.usermodel.DataValidationHelper;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.Drawing;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Name;
+import org.apache.poi.ss.usermodel.PictureData;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.apache.poi.xssf.usermodel.XSSFDrawing;
+import org.apache.poi.xssf.usermodel.XSSFPicture;
+import org.apache.poi.xssf.usermodel.XSSFShape;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.annotation.Excel.Type;
+import com.ruoyi.common.annotation.Excels;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.exception.UtilException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.DictUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.FileTypeUtils;
+import com.ruoyi.common.utils.file.FileUtils;
+import com.ruoyi.common.utils.file.ImageUtils;
+import com.ruoyi.common.utils.reflect.ReflectUtils;
+
+/**
+ * Excel相关处理
+ * 
+ * @author ruoyi
+ */
+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 = { "=", "-", "+", "@" };
+
+    /**
+     * 用于dictType属性数据存储,避免重复查缓存
+     */
+    public Map<String, String> sysDictMap = new HashMap<String, String>();
+
+    /**
+     * Excel sheet最大行数,默认65536
+     */
+    public static final int sheetSize = 65536;
+
+    /**
+     * 工作表名称
+     */
+    private String sheetName;
+
+    /**
+     * 导出类型(EXPORT:导出数据;IMPORT:导入模板)
+     */
+    private Type type;
+
+    /**
+     * 工作薄对象
+     */
+    private Workbook wb;
+
+    /**
+     * 工作表对象
+     */
+    private Sheet sheet;
+
+    /**
+     * 样式列表
+     */
+    private Map<String, CellStyle> styles;
+
+    /**
+     * 导入导出数据列表
+     */
+    private List<T> list;
+
+    /**
+     * 注解列表
+     */
+    private List<Object[]> fields;
+
+    /**
+     * 当前行号
+     */
+    private int rownum;
+
+    /**
+     * 标题
+     */
+    private String title;
+
+    /**
+     * 最大高度
+     */
+    private short maxHeight;
+
+    /**
+     * 合并后最后行数
+     */
+    private int subMergedLastRowNum = 0;
+
+    /**
+     * 合并后开始行数
+     */
+    private int subMergedFirstRowNum = 1;
+
+    /**
+     * 对象的子列表方法
+     */
+    private Method subMethod;
+
+    /**
+     * 对象的子列表属性
+     */
+    private List<Field> subFields;
+
+    /**
+     * 统计列表
+     */
+    private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
+
+    /**
+     * 数字格式
+     */
+    private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
+
+    /**
+     * 实体对象
+     */
+    public Class<T> clazz;
+
+    /**
+     * 需要排除列属性
+     */
+    public String[] excludeFields;
+
+    public ExcelUtil(Class<T> clazz)
+    {
+        this.clazz = clazz;
+    }
+
+    /**
+     * 隐藏Excel中列属性
+     *
+     * @param fields 列属性名 示例[单个"name"/多个"id","name"]
+     * @throws Exception
+     */
+    public void hideColumn(String... fields)
+    {
+        this.excludeFields = fields;
+    }
+
+    public void init(List<T> list, String sheetName, String title, Type type)
+    {
+        if (list == null)
+        {
+            list = new ArrayList<T>();
+        }
+        this.list = list;
+        this.sheetName = sheetName;
+        this.type = type;
+        this.title = title;
+        createExcelField();
+        createWorkbook();
+        createTitle();
+        createSubHead();
+    }
+
+    /**
+     * 创建excel第一行标题
+     */
+    public void createTitle()
+    {
+        if (StringUtils.isNotEmpty(title))
+        {
+            subMergedFirstRowNum++;
+            subMergedLastRowNum++;
+            int titleLastCol = this.fields.size() - 1;
+            if (isSubList())
+            {
+                titleLastCol = titleLastCol + subFields.size() - 1;
+            }
+            Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
+            titleRow.setHeightInPoints(30);
+            Cell titleCell = titleRow.createCell(0);
+            titleCell.setCellStyle(styles.get("title"));
+            titleCell.setCellValue(title);
+            sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol));
+        }
+    }
+
+    /**
+     * 创建对象的子列表名称
+     */
+    public void createSubHead()
+    {
+        if (isSubList())
+        {
+            subMergedFirstRowNum++;
+            subMergedLastRowNum++;
+            Row subRow = sheet.createRow(rownum);
+            int excelNum = 0;
+            for (Object[] objects : fields)
+            {
+                Excel attr = (Excel) objects[1];
+                Cell headCell1 = subRow.createCell(excelNum);
+                headCell1.setCellValue(attr.name());
+                headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));
+                excelNum++;
+            }
+            int headFirstRow = excelNum - 1;
+            int headLastRow = headFirstRow + subFields.size() - 1;
+            if (headLastRow > headFirstRow)
+            {
+                sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow));
+            }
+            rownum++;
+        }
+    }
+
+    /**
+     * 对excel表单默认第一个索引名转换成list
+     * 
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> importExcel(InputStream is)
+    {
+        List<T> list = null;
+        try
+        {
+            list = importExcel(is, 0);
+        }
+        catch (Exception e)
+        {
+            log.error("导入Excel异常{}", e.getMessage());
+            throw new UtilException(e.getMessage());
+        }
+        finally
+        {
+            IOUtils.closeQuietly(is);
+        }
+        return list;
+    }
+
+    /**
+     * 对excel表单默认第一个索引名转换成list
+     * 
+     * @param is 输入流
+     * @param titleNum 标题占用行数
+     * @return 转换后集合
+     */
+    public List<T> importExcel(InputStream is, int titleNum) throws Exception
+    {
+        return importExcel(StringUtils.EMPTY, is, titleNum);
+    }
+
+    /**
+     * 对excel表单指定表格索引名转换成list
+     * 
+     * @param sheetName 表格索引名
+     * @param titleNum 标题占用行数
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
+    {
+        this.type = 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)
+        {
+            throw new IOException("文件sheet不存在");
+        }
+        boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);
+        Map<String, PictureData> pictures;
+        if (isXSSFWorkbook)
+        {
+            pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);
+        }
+        else
+        {
+            pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);
+        }
+        // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1
+        int rows = sheet.getLastRowNum();
+        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++)
+            {
+                Cell cell = heard.getCell(i);
+                if (StringUtils.isNotNull(cell))
+                {
+                    String value = this.getCellValue(heard, i).toString();
+                    cellMap.put(value, i);
+                }
+                else
+                {
+                    cellMap.put(null, i);
+                }
+            }
+            // 有数据时才处理 得到类的所有field.
+            List<Object[]> fields = this.getFields();
+            Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
+            for (Object[] objects : fields)
+            {
+                Excel attr = (Excel) objects[1];
+                Integer column = cellMap.get(attr.name());
+                if (column != null)
+                {
+                    fieldsMap.put(column, objects);
+                }
+            }
+            for (int i = titleNum + 1; i <= rows; i++)
+            {
+                // 从第2行开始取数据,默认第一行是表头.
+                Row row = sheet.getRow(i);
+                // 判断当前行是否是空行
+                if (isRowEmpty(row))
+                {
+                    continue;
+                }
+                T entity = null;
+                for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet())
+                {
+                    Object val = this.getCellValue(row, entry.getKey());
+
+                    // 如果不存在实例则新建.
+                    entity = (entity == null ? clazz.getDeclaredConstructor().newInstance() : entity);
+                    // 从map中得到对应列的field.
+                    Field field = (Field) entry.getValue()[0];
+                    Excel attr = (Excel) entry.getValue()[1];
+                    // 取得类型,并根据对象类型设置值.
+                    Class<?> fieldType = field.getType();
+                    if (String.class == fieldType)
+                    {
+                        String s = Convert.toStr(val);
+                        if (StringUtils.endsWith(s, ".0"))
+                        {
+                            val = StringUtils.substringBefore(s, ".0");
+                        }
+                        else
+                        {
+                            String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                            if (StringUtils.isNotEmpty(dateFormat))
+                            {
+                                val = parseDateToStr(dateFormat, val);
+                            }
+                            else
+                            {
+                                val = 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)))
+                    {
+                        val = Convert.toLong(val);
+                    }
+                    else if (Double.TYPE == fieldType || Double.class == fieldType)
+                    {
+                        val = Convert.toDouble(val);
+                    }
+                    else if (Float.TYPE == fieldType || Float.class == fieldType)
+                    {
+                        val = Convert.toFloat(val);
+                    }
+                    else if (BigDecimal.class == fieldType)
+                    {
+                        val = Convert.toBigDecimal(val);
+                    }
+                    else if (Date.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            val = DateUtils.parseDate(val);
+                        }
+                        else if (val instanceof Double)
+                        {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    }
+                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
+                    {
+                        val = Convert.toBool(val, false);
+                    }
+                    if (StringUtils.isNotNull(fieldType))
+                    {
+                        String propertyName = field.getName();
+                        if (StringUtils.isNotEmpty(attr.targetAttr()))
+                        {
+                            propertyName = field.getName() + "." + attr.targetAttr();
+                        }
+                        if (StringUtils.isNotEmpty(attr.readConverterExp()))
+                        {
+                            val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
+                        }
+                        else if (StringUtils.isNotEmpty(attr.dictType()))
+                        {
+                            if (!sysDictMap.containsKey(attr.dictType() + val))
+                            {
+                                String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
+                                sysDictMap.put(attr.dictType() + val, dictValue);
+                            }
+                            val = sysDictMap.get(attr.dictType() + val);
+                        }
+                        else if (!attr.handler().equals(ExcelHandlerAdapter.class))
+                        {
+                            val = dataFormatHandlerAdapter(val, attr, null);
+                        }
+                        else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures))
+                        {
+                            PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());
+                            if (image == null)
+                            {
+                                val = "";
+                            }
+                            else
+                            {
+                                byte[] data = image.getData();
+                                val = FileUtils.writeImportBytes(data);
+                            }
+                        }
+                        ReflectUtils.invokeSetter(entity, propertyName, val);
+                    }
+                }
+                list.add(entity);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @param list 导出数据集合
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public AjaxResult exportExcel(List<T> list, String sheetName)
+    {
+        return exportExcel(list, sheetName, StringUtils.EMPTY);
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @param list 导出数据集合
+     * @param sheetName 工作表的名称
+     * @param title 标题
+     * @return 结果
+     */
+    public AjaxResult exportExcel(List<T> list, String sheetName, String title)
+    {
+        this.init(list, sheetName, title, Type.EXPORT);
+        return exportExcel();
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @param response 返回数据
+     * @param list 导出数据集合
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public void exportExcel(HttpServletResponse response, List<T> list, String sheetName)
+    {
+        exportExcel(response, list, sheetName, StringUtils.EMPTY);
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @param response 返回数据
+     * @param list 导出数据集合
+     * @param sheetName 工作表的名称
+     * @param title 标题
+     * @return 结果
+     */
+    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, Type.EXPORT);
+        exportExcel(response);
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public AjaxResult importTemplateExcel(String sheetName)
+    {
+        return importTemplateExcel(sheetName, StringUtils.EMPTY);
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @param sheetName 工作表的名称
+     * @param title 标题
+     * @return 结果
+     */
+    public AjaxResult importTemplateExcel(String sheetName, String title)
+    {
+        this.init(null, sheetName, title, Type.IMPORT);
+        return exportExcel();
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public void importTemplateExcel(HttpServletResponse response, String sheetName)
+    {
+        importTemplateExcel(response, sheetName, StringUtils.EMPTY);
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @param sheetName 工作表的名称
+     * @param title 标题
+     * @return 结果
+     */
+    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, Type.IMPORT);
+        exportExcel(response);
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @return 结果
+     */
+    public void exportExcel(HttpServletResponse response)
+    {
+        try
+        {
+            writeSheet();
+            wb.write(response.getOutputStream());
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel异常{}", e.getMessage());
+        }
+        finally
+        {
+            IOUtils.closeQuietly(wb);
+        }
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     * 
+     * @return 结果
+     */
+    public AjaxResult exportExcel()
+    {
+        OutputStream out = null;
+        try
+        {
+            writeSheet();
+            String filename = encodingFilename(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            wb.write(out);
+            return AjaxResult.success(filename);
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel异常{}", e.getMessage());
+            throw new UtilException("导出Excel失败,请联系网站管理员!");
+        }
+        finally
+        {
+            IOUtils.closeQuietly(wb);
+            IOUtils.closeQuietly(out);
+        }
+    }
+
+    /**
+     * 创建写入数据到Sheet
+     */
+    public void writeSheet()
+    {
+        // 取出一共有多少个sheet.
+        int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));
+        for (int index = 0; index < sheetNo; index++)
+        {
+            createSheet(sheetNo, index);
+
+            // 产生一行
+            Row row = sheet.createRow(rownum);
+            int column = 0;
+            // 写入各个字段的列头名称
+            for (Object[] os : fields)
+            {
+                Field field = (Field) os[0];
+                Excel excel = (Excel) os[1];
+                if (Collection.class.isAssignableFrom(field.getType()))
+                {
+                    for (Field subField : subFields)
+                    {
+                        Excel subExcel = subField.getAnnotation(Excel.class);
+                        this.createHeadCell(subExcel, row, column++);
+                    }
+                }
+                else
+                {
+                    this.createHeadCell(excel, row, column++);
+                }
+            }
+            if (Type.EXPORT.equals(type))
+            {
+                fillExcelData(index, row);
+                addStatisticsRow();
+            }
+        }
+    }
+
+    /**
+     * 填充excel数据
+     * 
+     * @param index 序号
+     * @param row 单元格行
+     */
+    @SuppressWarnings("unchecked")
+    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++)
+        {
+            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))
+                {
+                    subList = getListCellValue(vo);
+                    subMergedLastRowNum = subMergedLastRowNum + subList.size();
+                }
+                else
+                {
+                    subMergedFirstRowNum++;
+                    subMergedLastRowNum++;
+                }
+            }
+            int column = 0;
+            for (Object[] os : fields)
+            {
+                Field field = (Field) os[0];
+                Excel excel = (Excel) os[1];
+                if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList))
+                {
+                    boolean subFirst = false;
+                    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))
+                            {
+                                subField.setAccessible(true);
+                                Excel attr = subField.getAnnotation(Excel.class);
+                                this.addCell(attr, row, (T) obj, subField, column + subIndex);
+                            }
+                            subIndex++;
+                        }
+                        subFirst = true;
+                    }
+                    this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size();
+                }
+                else
+                {
+                    this.addCell(excel, row, vo, field, column++);
+                }
+            }
+        }
+    }
+
+    /**
+     * 创建表格样式
+     * 
+     * @param wb 工作薄对象
+     * @return 样式列表
+     */
+    private Map<String, CellStyle> createStyles(Workbook wb)
+    {
+        // 写入各条记录,每条记录对应excel表中的一行
+        Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
+        CellStyle style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        Font titleFont = wb.createFont();
+        titleFont.setFontName("Arial");
+        titleFont.setFontHeightInPoints((short) 16);
+        titleFont.setBold(true);
+        style.setFont(titleFont);
+        DataFormat dataFormat = wb.createDataFormat();
+        style.setDataFormat(dataFormat.getFormat("@"));
+        styles.put("title", style);
+
+        style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderTop(BorderStyle.THIN);
+        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        Font dataFont = wb.createFont();
+        dataFont.setFontName("Arial");
+        dataFont.setFontHeightInPoints((short) 10);
+        style.setFont(dataFont);
+        styles.put("data", style);
+
+        style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        Font totalFont = wb.createFont();
+        totalFont.setFontName("Arial");
+        totalFont.setFontHeightInPoints((short) 10);
+        style.setFont(totalFont);
+        styles.put("total", style);
+
+        styles.putAll(annotationHeaderStyles(wb, styles));
+
+        styles.putAll(annotationDataStyles(wb));
+
+        return styles;
+    }
+
+    /**
+     * 根据Excel注解创建表格头样式
+     * 
+     * @param wb 工作薄对象
+     * @return 自定义样式列表
+     */
+    private Map<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles)
+    {
+        Map<String, CellStyle> headerStyles = new HashMap<String, CellStyle>();
+        for (Object[] os : fields)
+        {
+            Excel excel = (Excel) os[1];
+            String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor());
+            if (!headerStyles.containsKey(key))
+            {
+                CellStyle style = wb.createCellStyle();
+                style.cloneStyleFrom(styles.get("data"));
+                style.setAlignment(HorizontalAlignment.CENTER);
+                style.setVerticalAlignment(VerticalAlignment.CENTER);
+                style.setFillForegroundColor(excel.headerBackgroundColor().index);
+                style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+                Font headerFont = wb.createFont();
+                headerFont.setFontName("Arial");
+                headerFont.setFontHeightInPoints((short) 10);
+                headerFont.setBold(true);
+                headerFont.setColor(excel.headerColor().index);
+                style.setFont(headerFont);
+                // 设置表格头单元格文本形式
+                DataFormat dataFormat = wb.createDataFormat();
+                style.setDataFormat(dataFormat.getFormat("@"));
+                headerStyles.put(key, style);
+            }
+        }
+        return headerStyles;
+    }
+
+    /**
+     * 根据Excel注解创建表格列样式
+     * 
+     * @param wb 工作薄对象
+     * @return 自定义样式列表
+     */
+    private Map<String, CellStyle> annotationDataStyles(Workbook wb)
+    {
+        Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
+        for (Object[] os : fields)
+        {
+            Field field = (Field) os[0];
+            Excel excel = (Excel) os[1];
+            if (Collection.class.isAssignableFrom(field.getType()))
+            {
+                ParameterizedType pt = (ParameterizedType) field.getGenericType();
+                Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
+                List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
+                for (Field subField : subFields)
+                {
+                    Excel subExcel = subField.getAnnotation(Excel.class);
+                    annotationDataStyles(styles, subField, subExcel);
+                }
+            }
+            else
+            {
+                annotationDataStyles(styles, field, excel);
+            }
+        }
+        return styles;
+    }
+
+    /**
+     * 根据Excel注解创建表格列样式
+     * 
+     * @param styles 自定义样式列表
+     * @param field  属性列信息
+     * @param excel  注解信息
+     */
+    public void annotationDataStyles(Map<String, CellStyle> styles, Field field, Excel excel)
+    {
+        String key = StringUtils.format("data_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType());
+        if (!styles.containsKey(key))
+        {
+            CellStyle style = wb.createCellStyle();
+            style.setAlignment(excel.align());
+            style.setVerticalAlignment(VerticalAlignment.CENTER);
+            style.setBorderRight(BorderStyle.THIN);
+            style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+            style.setBorderLeft(BorderStyle.THIN);
+            style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+            style.setBorderTop(BorderStyle.THIN);
+            style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+            style.setBorderBottom(BorderStyle.THIN);
+            style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            style.setFillForegroundColor(excel.backgroundColor().getIndex());
+            Font dataFont = wb.createFont();
+            dataFont.setFontName("Arial");
+            dataFont.setFontHeightInPoints((short) 10);
+            dataFont.setColor(excel.color().index);
+            style.setFont(dataFont);
+            if (ColumnType.TEXT == excel.cellType())
+            {
+                DataFormat dataFormat = wb.createDataFormat();
+                style.setDataFormat(dataFormat.getFormat("@"));
+            }
+            styles.put(key, style);
+        }
+    }
+
+    /**
+     * 创建单元格
+     */
+    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())
+        {
+            // 填充默认样式,防止合并单元格样式失效
+            sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType())));
+            if (attr.needMerge())
+            {
+                sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column));
+            }
+        }
+        return cell;
+    }
+
+    /**
+     * 设置单元格信息
+     * 
+     * @param value 单元格值
+     * @param attr 注解相关
+     * @param cell 单元格信息
+     */
+    public void setCellVo(Object value, Excel attr, Cell cell)
+    {
+        if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType())
+        {
+            String cellValue = Convert.toStr(value);
+            // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。
+            if (StringUtils.startsWithAny(cellValue, FORMULA_STR))
+            {
+                cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0");
+            }
+            if (value instanceof Collection && StringUtils.equals("[]", cellValue))
+            {
+                cellValue = StringUtils.EMPTY;
+            }
+            cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix());
+        }
+        else if (ColumnType.NUMERIC == attr.cellType())
+        {
+            if (StringUtils.isNotNull(value))
+            {
+                cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));
+            }
+        }
+        else if (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))
+            {
+                byte[] data = ImageUtils.getImage(imagePath);
+                getDrawingPatriarch(cell.getSheet()).createPicture(anchor,
+                        cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
+            }
+        }
+    }
+
+    /**
+     * 获取画布
+     */
+    public static Drawing<?> getDrawingPatriarch(Sheet sheet)
+    {
+        if (sheet.getDrawingPatriarch() == null)
+        {
+            sheet.createDrawingPatriarch();
+        }
+        return sheet.getDrawingPatriarch();
+    }
+
+    /**
+     * 获取图片类型,设置图片插入类型
+     */
+    public int getImageType(byte[] value)
+    {
+        String type = FileTypeUtils.getFileExtendName(value);
+        if ("JPG".equalsIgnoreCase(type))
+        {
+            return Workbook.PICTURE_TYPE_JPEG;
+        }
+        else if ("PNG".equalsIgnoreCase(type))
+        {
+            return Workbook.PICTURE_TYPE_PNG;
+        }
+        return Workbook.PICTURE_TYPE_JPEG;
+    }
+
+    /**
+     * 创建表格样式
+     */
+    public void setDataValidation(Excel attr, Row row, int column)
+    {
+        if (attr.name().indexOf("注:") >= 0)
+        {
+            sheet.setColumnWidth(column, 6000);
+        }
+        else
+        {
+            // 设置列宽
+            sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
+        }
+        if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict())
+        {
+            String[] comboArray = attr.combo();
+            if (attr.comboReadDict())
+            {
+                if (!sysDictMap.containsKey("combo_" + attr.dictType()))
+                {
+                    String labels = DictUtils.getDictLabels(attr.dictType());
+                    sysDictMap.put("combo_" + attr.dictType(), labels);
+                }
+                String val = sysDictMap.get("combo_" + attr.dictType());
+                comboArray = StringUtils.split(val, DictUtils.SEPARATOR);
+            }
+            if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255)
+            {
+                // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到
+                setXSSFValidationWithHidden(sheet, comboArray, attr.prompt(), 1, 100, column, column);
+            }
+            else
+            {
+                // 提示信息或只能选择不能输入的列内容.
+                setPromptOrValidation(sheet, comboArray, attr.prompt(), 1, 100, column, column);
+            }
+        }
+    }
+
+    /**
+     * 添加单元格
+     */
+    public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
+    {
+        Cell cell = null;
+        try
+        {
+            // 设置行高
+            row.setHeight(maxHeight);
+            // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
+            if (attr.isExport())
+            {
+                // 创建cell
+                cell = row.createCell(column);
+                if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge())
+                {
+                    CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column);
+                    sheet.addMergedRegion(cellAddress);
+                }
+                cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType())));
+
+                // 用于读取对象中的属性
+                Object value = getTargetValue(vo, field, attr);
+                String dateFormat = attr.dateFormat();
+                String readConverterExp = attr.readConverterExp();
+                String separator = attr.separator();
+                String dictType = attr.dictType();
+                if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
+                {
+                    cell.setCellValue(parseDateToStr(dateFormat, 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))
+                    {
+                        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())
+                {
+                    cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());
+                }
+                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)
+        {
+            log.error("导出Excel失败{}", e);
+        }
+        return cell;
+    }
+
+    /**
+     * 设置 POI XSSFSheet 单元格提示或选择框
+     * 
+     * @param sheet 表单
+     * @param textlist 下拉框显示的内容
+     * @param promptContent 提示内容
+     * @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)
+    {
+        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))
+        {
+            // 如果设置了提示信息则鼠标放上去提示
+            dataValidation.createPromptBox("", promptContent);
+            dataValidation.setShowPromptBox(true);
+        }
+        // 处理Excel兼容性问题
+        if (dataValidation instanceof XSSFDataValidation)
+        {
+            dataValidation.setSuppressDropDownArrow(true);
+            dataValidation.setShowErrorBox(true);
+        }
+        else
+        {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框).
+     * 
+     * @param sheet 要设置的sheet.
+     * @param textlist 下拉框显示的内容
+     * @param promptContent 提示内容
+     * @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)
+    {
+        String hideSheetName = "combo_" + firstCol + "_" + endCol;
+        Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据
+        for (int i = 0; i < textlist.length; i++)
+        {
+            hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]);
+        }
+        // 创建名称,可被其他单元格引用
+        Name name = wb.createName();
+        name.setNameName(hideSheetName + "_data");
+        name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length);
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        // 加载下拉列表内容
+        DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data");
+        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        // 数据有效性对象
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        if (StringUtils.isNotEmpty(promptContent))
+        {
+            // 如果设置了提示信息则鼠标放上去提示
+            dataValidation.createPromptBox("", promptContent);
+            dataValidation.setShowPromptBox(true);
+        }
+        // 处理Excel兼容性问题
+        if (dataValidation instanceof XSSFDataValidation)
+        {
+            dataValidation.setSuppressDropDownArrow(true);
+            dataValidation.setShowErrorBox(true);
+        }
+        else
+        {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+
+        sheet.addValidationData(dataValidation);
+        // 设置hiddenSheet隐藏
+        wb.setSheetHidden(wb.getSheetIndex(hideSheet), true);
+    }
+
+    /**
+     * 解析导出值 0=男,1=女,2=未知
+     * 
+     * @param propertyValue 参数值
+     * @param converterExp 翻译注解
+     * @param separator 分隔符
+     * @return 解析后值
+     */
+    public static String convertByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            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))
+                {
+                    return itemArray[1];
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 反向解析值 男=0,女=1,未知=2
+     * 
+     * @param propertyValue 参数值
+     * @param converterExp 翻译注解
+     * @param separator 分隔符
+     * @return 解析后值
+     */
+    public static String reverseByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            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))
+                {
+                    return itemArray[0];
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 解析字典值
+     * 
+     * @param dictValue 字典值
+     * @param dictType 字典类型
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    public static String convertDictByExp(String dictValue, String dictType, String separator)
+    {
+        return DictUtils.getDictLabel(dictType, dictValue, separator);
+    }
+
+    /**
+     * 反向解析值字典值
+     * 
+     * @param dictLabel 字典标签
+     * @param dictType 字典类型
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    public static String reverseDictByExp(String dictLabel, String dictType, String separator)
+    {
+        return DictUtils.getDictValue(dictType, dictLabel, separator);
+    }
+
+    /**
+     * 数据处理器
+     * 
+     * @param value 数据值
+     * @param excel 数据注解
+     * @return
+     */
+    public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell)
+    {
+        try
+        {
+            Object instance = excel.handler().getDeclaredConstructor().newInstance();
+            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);
+    }
+
+    /**
+     * 合计统计信息
+     */
+    private void addStatisticsData(Integer index, String text, Excel entity)
+    {
+        if (entity != null && entity.isStatistics())
+        {
+            Double temp = 0D;
+            if (!statistics.containsKey(index))
+            {
+                statistics.put(index, temp);
+            }
+            try
+            {
+                temp = Double.valueOf(text);
+            }
+            catch (NumberFormatException e)
+            {
+            }
+            statistics.put(index, statistics.get(index) + temp);
+        }
+    }
+
+    /**
+     * 创建统计行
+     */
+    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)
+            {
+                cell = row.createCell(key);
+                cell.setCellStyle(styles.get("total"));
+                cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
+            }
+            statistics.clear();
+        }
+    }
+
+    /**
+     * 编码文件名
+     */
+    public String encodingFilename(String filename)
+    {
+        filename = UUID.randomUUID() + "_" + filename + ".xlsx";
+        return filename;
+    }
+
+    /**
+     * 获取下载路径
+     * 
+     * @param filename 文件名称
+     */
+    public String getAbsoluteFile(String filename)
+    {
+        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
+        File desc = new File(downloadPath);
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        return downloadPath;
+    }
+
+    /**
+     * 获取bean中的属性值
+     * 
+     * @param vo 实体对象
+     * @param field 字段
+     * @param excel 注解
+     * @return 最终的属性值
+     * @throws Exception
+     */
+    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
+    {
+        Object o = field.get(vo);
+        if (StringUtils.isNotEmpty(excel.targetAttr()))
+        {
+            String target = excel.targetAttr();
+            if (target.contains("."))
+            {
+                String[] targets = target.split("[.]");
+                for (String name : targets)
+                {
+                    o = getValue(o, name);
+                }
+            }
+            else
+            {
+                o = getValue(o, target);
+            }
+        }
+        return o;
+    }
+
+    /**
+     * 以类的属性的get方法方法形式获取值
+     * 
+     * @param o
+     * @param name
+     * @return value
+     * @throws Exception
+     */
+    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);
+            o = field.get(o);
+        }
+        return o;
+    }
+
+    /**
+     * 得到所有定义字段
+     */
+    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();
+    }
+
+    /**
+     * 获取字段注解信息
+     */
+    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()))
+            {
+                // 单注解
+                if (field.isAnnotationPresent(Excel.class))
+                {
+                    Excel attr = field.getAnnotation(Excel.class);
+                    if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+                    {
+                        field.setAccessible(true);
+                        fields.add(new Object[] { field, attr });
+                    }
+                    if (Collection.class.isAssignableFrom(field.getType()))
+                    {
+                        subMethod = getSubMethod(field.getName(), clazz);
+                        ParameterizedType pt = (ParameterizedType) field.getGenericType();
+                        Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
+                        this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
+                    }
+                }
+
+                // 多注解
+                if (field.isAnnotationPresent(Excels.class))
+                {
+                    Excels attrs = field.getAnnotation(Excels.class);
+                    Excel[] excels = attrs.value();
+                    for (Excel attr : excels)
+                    {
+                        if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr())
+                                && (attr != null && (attr.type() == Type.ALL || attr.type() == type)))
+                        {
+                            field.setAccessible(true);
+                            fields.add(new Object[] { field, attr });
+                        }
+                    }
+                }
+            }
+        }
+        return fields;
+    }
+
+    /**
+     * 根据注解获取最大行高
+     */
+    public short getRowHeight()
+    {
+        double maxHeight = 0;
+        for (Object[] os : this.fields)
+        {
+            Excel excel = (Excel) os[1];
+            maxHeight = Math.max(maxHeight, excel.height());
+        }
+        return (short) (maxHeight * 20);
+    }
+
+    /**
+     * 创建一个工作簿
+     */
+    public void createWorkbook()
+    {
+        this.wb = new SXSSFWorkbook(500);
+        this.sheet = wb.createSheet();
+        wb.setSheetName(0, sheetName);
+        this.styles = createStyles(wb);
+    }
+
+    /**
+     * 创建工作表
+     * 
+     * @param sheetNo sheet数量
+     * @param index 序号
+     */
+    public void createSheet(int sheetNo, int index)
+    {
+        // 设置工作表的名称.
+        if (sheetNo > 1 && index > 0)
+        {
+            this.sheet = wb.createSheet();
+            this.createTitle();
+            wb.setSheetName(index, sheetName + index);
+        }
+    }
+
+    /**
+     * 获取单元格值
+     * 
+     * @param row 获取的行
+     * @param column 获取单元格列号
+     * @return 单元格值
+     */
+    public Object getCellValue(Row row, int column)
+    {
+        if (row == null)
+        {
+            return row;
+        }
+        Object val = "";
+        try
+        {
+            Cell cell = row.getCell(column);
+            if (StringUtils.isNotNull(cell))
+            {
+                if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA)
+                {
+                    val = cell.getNumericCellValue();
+                    if (DateUtil.isCellDateFormatted(cell))
+                    {
+                        val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换
+                    }
+                    else
+                    {
+                        if ((Double) val % 1 != 0)
+                        {
+                            val = new BigDecimal(val.toString());
+                        }
+                        else
+                        {
+                            val = new DecimalFormat("0").format(val);
+                        }
+                    }
+                }
+                else if (cell.getCellType() == CellType.STRING)
+                {
+                    val = cell.getStringCellValue();
+                }
+                else if (cell.getCellType() == CellType.BOOLEAN)
+                {
+                    val = cell.getBooleanCellValue();
+                }
+                else if (cell.getCellType() == CellType.ERROR)
+                {
+                    val = cell.getErrorCellValue();
+                }
+
+            }
+        }
+        catch (Exception e)
+        {
+            return val;
+        }
+        return val;
+    }
+
+    /**
+     * 判断是否是空行
+     * 
+     * @param row 判断的行
+     * @return
+     */
+    private boolean isRowEmpty(Row row)
+    {
+        if (row == null)
+        {
+            return true;
+        }
+        for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++)
+        {
+            Cell cell = row.getCell(i);
+            if (cell != null && cell.getCellType() != CellType.BLANK)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 获取Excel2003图片
+     *
+     * @param sheet 当前sheet对象
+     * @param workbook 工作簿对象
+     * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData
+     */
+    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())
+            {
+                HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor();
+                if (shape instanceof HSSFPicture)
+                {
+                    HSSFPicture pic = (HSSFPicture) shape;
+                    int pictureIndex = pic.getPictureIndex() - 1;
+                    HSSFPictureData picData = pictures.get(pictureIndex);
+                    String picIndex = anchor.getRow1() + "_" + anchor.getCol1();
+                    sheetIndexPicMap.put(picIndex, picData);
+                }
+            }
+            return sheetIndexPicMap;
+        }
+        else
+        {
+            return sheetIndexPicMap;
+        }
+    }
+
+    /**
+     * 获取Excel2007图片
+     *
+     * @param sheet 当前sheet对象
+     * @param workbook 工作簿对象
+     * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData
+     */
+    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)
+            {
+                XSSFDrawing drawing = (XSSFDrawing) dr;
+                List<XSSFShape> shapes = drawing.getShapes();
+                for (XSSFShape shape : shapes)
+                {
+                    if (shape instanceof XSSFPicture)
+                    {
+                        XSSFPicture pic = (XSSFPicture) shape;
+                        XSSFClientAnchor anchor = pic.getPreferredSize();
+                        CTMarker ctMarker = anchor.getFrom();
+                        String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol();
+                        sheetIndexPicMap.put(picIndex, pic.getPictureData());
+                    }
+                }
+            }
+        }
+        return sheetIndexPicMap;
+    }
+
+    /**
+     * 格式化不同类型的日期对象
+     * 
+     * @param dateFormat 日期格式
+     * @param val 被格式化的日期对象
+     * @return 格式化后的日期字符
+     */
+    public String parseDateToStr(String dateFormat, Object val)
+    {
+        if (val == null)
+        {
+            return "";
+        }
+        String str;
+        if (val instanceof Date)
+        {
+            str = DateUtils.parseDateToStr(dateFormat, (Date) val);
+        }
+        else if (val instanceof LocalDateTime)
+        {
+            str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val));
+        }
+        else if (val instanceof LocalDate)
+        {
+            str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val));
+        }
+        else
+        {
+            str = val.toString();
+        }
+        return str;
+    }
+
+    /**
+     * 是否有对象的子列表
+     */
+    public boolean isSubList()
+    {
+        return StringUtils.isNotNull(subFields) && subFields.size() > 0;
+    }
+
+    /**
+     * 是否有对象的子列表,集合不为空
+     */
+    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)
+    {
+        Object value;
+        try
+        {
+            value = subMethod.invoke(obj, new Object[] {});
+        }
+        catch (Exception e)
+        {
+            return new ArrayList<Object>();
+        }
+        return (Collection<?>) value;
+    }
+
+    /**
+     * 获取对象的子列表方法
+     * 
+     * @param name 名称
+     * @param pojoClass 类对象
+     * @return 子列表方法
+     */
+    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)
+        {
+            log.error("获取对象异常{}", e.getMessage());
+        }
+        return method;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java
new file mode 100644
index 0000000..b19953e
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java
@@ -0,0 +1,410 @@
+package com.ruoyi.common.utils.reflect;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Date;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.utils.DateUtils;
+
+/**
+ * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
+ * 
+ * @author ruoyi
+ */
+@SuppressWarnings("rawtypes")
+public class ReflectUtils
+{
+    private static final String SETTER_PREFIX = "set";
+
+    private static final String GETTER_PREFIX = "get";
+
+    private static final String CGLIB_CLASS_SEPARATOR = "$$";
+
+    private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class);
+
+    /**
+     * 调用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 = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+        }
+        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 = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+            }
+            else
+            {
+                String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+                invokeMethodByName(object, setterMethodName, new Object[] { value });
+            }
+        }
+    }
+
+    /**
+     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E getFieldValue(final Object obj, final String fieldName)
+    {
+        Field field = getAccessibleField(obj, fieldName);
+        if (field == null)
+        {
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            return null;
+        }
+        E result = null;
+        try
+        {
+            result = (E) field.get(obj);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.error("不可能抛出的异常{}", e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
+     */
+    public static <E> void setFieldValue(final Object obj, final String fieldName, final E value)
+    {
+        Field field = getAccessibleField(obj, fieldName);
+        if (field == null)
+        {
+            // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            return;
+        }
+        try
+        {
+            field.set(obj, value);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.error("不可能抛出的异常: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符.
+     * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
+     * 同时匹配方法名+参数类型,
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
+            final Object[] args)
+    {
+        if (obj == null || methodName == null)
+        {
+            return null;
+        }
+        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
+        if (method == null)
+        {
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
+            return null;
+        }
+        try
+        {
+            return (E) method.invoke(obj, args);
+        }
+        catch (Exception e)
+        {
+            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
+            throw convertReflectionExceptionToUnchecked(msg, e);
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符,
+     * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
+     * 只匹配函数名,如果有多个同名函数调用第一个。
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeMethodByName(final Object obj, final String methodName, final Object[] args)
+    {
+        Method method = getAccessibleMethodByName(obj, methodName, args.length);
+        if (method == null)
+        {
+            // 如果为空不报错,直接返回空。
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
+            return null;
+        }
+        try
+        {
+            // 类型转换(将参数数据类型转换为目标方法参数类型)
+            Class<?>[] cs = method.getParameterTypes();
+            for (int i = 0; i < cs.length; i++)
+            {
+                if (args[i] != null && !args[i].getClass().equals(cs[i]))
+                {
+                    if (cs[i] == String.class)
+                    {
+                        args[i] = Convert.toStr(args[i]);
+                        if (StringUtils.endsWith((String) args[i], ".0"))
+                        {
+                            args[i] = StringUtils.substringBefore((String) args[i], ".0");
+                        }
+                    }
+                    else if (cs[i] == Integer.class)
+                    {
+                        args[i] = Convert.toInt(args[i]);
+                    }
+                    else if (cs[i] == Long.class)
+                    {
+                        args[i] = Convert.toLong(args[i]);
+                    }
+                    else if (cs[i] == Double.class)
+                    {
+                        args[i] = Convert.toDouble(args[i]);
+                    }
+                    else if (cs[i] == Float.class)
+                    {
+                        args[i] = Convert.toFloat(args[i]);
+                    }
+                    else if (cs[i] == Date.class)
+                    {
+                        if (args[i] instanceof String)
+                        {
+                            args[i] = DateUtils.parseDate(args[i]);
+                        }
+                        else
+                        {
+                            args[i] = DateUtil.getJavaDate((Double) args[i]);
+                        }
+                    }
+                    else if (cs[i] == boolean.class || cs[i] == Boolean.class)
+                    {
+                        args[i] = Convert.toBool(args[i]);
+                    }
+                }
+            }
+            return (E) method.invoke(obj, args);
+        }
+        catch (Exception e)
+        {
+            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
+            throw convertReflectionExceptionToUnchecked(msg, e);
+        }
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     */
+    public static Field getAccessibleField(final Object obj, final String fieldName)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(fieldName, "fieldName can't be blank");
+        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass())
+        {
+            try
+            {
+                Field field = superClass.getDeclaredField(fieldName);
+                makeAccessible(field);
+                return field;
+            }
+            catch (NoSuchFieldException e)
+            {
+                continue;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 匹配函数名+参数类型。
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethod(final Object obj, final String methodName,
+            final Class<?>... parameterTypes)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(methodName, "methodName can't be blank");
+        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
+        {
+            try
+            {
+                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
+                makeAccessible(method);
+                return method;
+            }
+            catch (NoSuchMethodException e)
+            {
+                continue;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 只匹配函数名。
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(methodName, "methodName can't be blank");
+        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
+        {
+            Method[] methods = searchType.getDeclaredMethods();
+            for (Method method : methods)
+            {
+                if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum)
+                {
+                    makeAccessible(method);
+                    return method;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+     */
+    public static void makeAccessible(Method method)
+    {
+        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
+                && !method.isAccessible())
+        {
+            method.setAccessible(true);
+        }
+    }
+
+    /**
+     * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+     */
+    public static void makeAccessible(Field field)
+    {
+        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
+                || Modifier.isFinal(field.getModifiers())) && !field.isAccessible())
+        {
+            field.setAccessible(true);
+        }
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
+     * 如无法找到, 返回Object.class.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Class<T> getClassGenricType(final Class clazz)
+    {
+        return getClassGenricType(clazz, 0);
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
+     * 如无法找到, 返回Object.class.
+     */
+    public static Class getClassGenricType(final Class clazz, final int index)
+    {
+        Type genType = clazz.getGenericSuperclass();
+
+        if (!(genType instanceof ParameterizedType))
+        {
+            logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType");
+            return Object.class;
+        }
+
+        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
+
+        if (index >= params.length || index < 0)
+        {
+            logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+                    + params.length);
+            return Object.class;
+        }
+        if (!(params[index] instanceof Class))
+        {
+            logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
+            return Object.class;
+        }
+
+        return (Class) params[index];
+    }
+
+    public static Class<?> getUserClass(Object instance)
+    {
+        if (instance == null)
+        {
+            throw new RuntimeException("Instance must not be null");
+        }
+        Class clazz = instance.getClass();
+        if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR))
+        {
+            Class<?> superClass = clazz.getSuperclass();
+            if (superClass != null && !Object.class.equals(superClass))
+            {
+                return superClass;
+            }
+        }
+        return clazz;
+
+    }
+
+    /**
+     * 将反射时的checked exception转换为unchecked exception.
+     */
+    public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e)
+    {
+        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
+                || e instanceof NoSuchMethodException)
+        {
+            return new IllegalArgumentException(msg, e);
+        }
+        else if (e instanceof InvocationTargetException)
+        {
+            return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());
+        }
+        return new RuntimeException(msg, e);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java
new file mode 100644
index 0000000..ca1cd92
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java
@@ -0,0 +1,291 @@
+package com.ruoyi.common.utils.sign;
+
+/**
+ * Base64工具类
+ * 
+ * @author ruoyi
+ */
+public final class Base64
+{
+    static private final int     BASELENGTH           = 128;
+    static private final int     LOOKUPLENGTH         = 64;
+    static private final int     TWENTYFOURBITGROUP   = 24;
+    static private final int     EIGHTBIT             = 8;
+    static private final int     SIXTEENBIT           = 16;
+    static private final int     FOURBYTE             = 4;
+    static private final int     SIGN                 = -128;
+    static private final char    PAD                  = '=';
+    static final private byte[]  base64Alphabet       = new byte[BASELENGTH];
+    static final private char[]  lookUpBase64Alphabet = new char[LOOKUPLENGTH];
+
+    static
+    {
+        for (int i = 0; i < BASELENGTH; ++i)
+        {
+            base64Alphabet[i] = -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+
+        for (int i = '9'; i >= '0'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+
+        for (int i = 0; i <= 25; i++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('A' + i);
+        }
+
+        for (int i = 26, j = 0; i <= 51; i++, j++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('a' + j);
+        }
+
+        for (int i = 52, j = 0; i <= 61; i++, j++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('0' + j);
+        }
+        lookUpBase64Alphabet[62] = (char) '+';
+        lookUpBase64Alphabet[63] = (char) '/';
+    }
+
+    private static boolean isWhiteSpace(char octect)
+    {
+        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
+    }
+
+    private static boolean isPad(char octect)
+    {
+        return (octect == PAD);
+    }
+
+    private static boolean isData(char octect)
+    {
+        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
+    }
+
+    /**
+     * Encodes hex octects into Base64
+     *
+     * @param binaryData Array containing binaryData
+     * @return Encoded Base64 array
+     */
+    public static String encode(byte[] binaryData)
+    {
+        if (binaryData == null)
+        {
+            return null;
+        }
+
+        int lengthDataBits = binaryData.length * EIGHTBIT;
+        if (lengthDataBits == 0)
+        {
+            return "";
+        }
+
+        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
+        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
+        char encodedData[] = null;
+
+        encodedData = new char[numberQuartet * 4];
+
+        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+
+        for (int i = 0; i < numberTriplets; i++)
+        {
+            b1 = binaryData[dataIndex++];
+            b2 = binaryData[dataIndex++];
+            b3 = binaryData[dataIndex++];
+
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
+        }
+
+        // form integral number of 6-bit groups
+        if (fewerThan24bits == EIGHTBIT)
+        {
+            b1 = binaryData[dataIndex];
+            k = (byte) (b1 & 0x03);
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
+            encodedData[encodedIndex++] = PAD;
+            encodedData[encodedIndex++] = PAD;
+        }
+        else if (fewerThan24bits == SIXTEENBIT)
+        {
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
+            encodedData[encodedIndex++] = PAD;
+        }
+        return new String(encodedData);
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param encoded string containing Base64 data
+     * @return Array containind decoded data.
+     */
+    public static byte[] decode(String encoded)
+    {
+        if (encoded == null)
+        {
+            return null;
+        }
+
+        char[] base64Data = encoded.toCharArray();
+        // remove white spaces
+        int len = removeWhiteSpace(base64Data);
+
+        if (len % FOURBYTE != 0)
+        {
+            return null;// should be divisible by four
+        }
+
+        int numberQuadruple = (len / FOURBYTE);
+
+        if (numberQuadruple == 0)
+        {
+            return new byte[0];
+        }
+
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
+        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
+
+        int i = 0;
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        decodedData = new byte[(numberQuadruple) * 3];
+
+        for (; i < numberQuadruple - 1; i++)
+        {
+
+            if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
+                    || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++])))
+            {
+                return null;
+            } // if found "no data" just return null
+
+            b1 = base64Alphabet[d1];
+            b2 = base64Alphabet[d2];
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+        }
+
+        if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])))
+        {
+            return null;// if found "no data" just return null
+        }
+
+        b1 = base64Alphabet[d1];
+        b2 = base64Alphabet[d2];
+
+        d3 = base64Data[dataIndex++];
+        d4 = base64Data[dataIndex++];
+        if (!isData((d3)) || !isData((d4)))
+        {// Check if they are PAD characters
+            if (isPad(d3) && isPad(d4))
+            {
+                if ((b2 & 0xf) != 0)// last 4 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 1];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                return tmp;
+            }
+            else if (!isPad(d3) && isPad(d4))
+            {
+                b3 = base64Alphabet[d3];
+                if ((b3 & 0x3) != 0)// last 2 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 2];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                return tmp;
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else
+        { // No PAD e.g 3cQl
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+
+        }
+        return decodedData;
+    }
+
+    /**
+     * remove WhiteSpace from MIME containing encoded Base64 data.
+     *
+     * @param data the byte array of base64 data (with WS)
+     * @return the new length
+     */
+    private static int removeWhiteSpace(char[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        // count characters that's not whitespace
+        int newSize = 0;
+        int len = data.length;
+        for (int i = 0; i < len; i++)
+        {
+            if (!isWhiteSpace(data[i]))
+            {
+                data[newSize++] = data[i];
+            }
+        }
+        return newSize;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java
new file mode 100644
index 0000000..c1c58db
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java
@@ -0,0 +1,67 @@
+package com.ruoyi.common.utils.sign;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Md5加密方法
+ * 
+ * @author ruoyi
+ */
+public class Md5Utils
+{
+    private static final Logger log = LoggerFactory.getLogger(Md5Utils.class);
+
+    private static byte[] md5(String s)
+    {
+        MessageDigest algorithm;
+        try
+        {
+            algorithm = MessageDigest.getInstance("MD5");
+            algorithm.reset();
+            algorithm.update(s.getBytes("UTF-8"));
+            byte[] messageDigest = algorithm.digest();
+            return messageDigest;
+        }
+        catch (Exception e)
+        {
+            log.error("MD5 Error...", e);
+        }
+        return null;
+    }
+
+    private static final String toHex(byte hash[])
+    {
+        if (hash == null)
+        {
+            return null;
+        }
+        StringBuffer buf = new StringBuffer(hash.length * 2);
+        int i;
+
+        for (i = 0; i < hash.length; i++)
+        {
+            if ((hash[i] & 0xff) < 0x10)
+            {
+                buf.append("0");
+            }
+            buf.append(Long.toString(hash[i] & 0xff, 16));
+        }
+        return buf.toString();
+    }
+
+    public static String hash(String s)
+    {
+        try
+        {
+            return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
+        }
+        catch (Exception e)
+        {
+            log.error("not supported charset...{}", e);
+            return s;
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java
new file mode 100644
index 0000000..f290ec3
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java
@@ -0,0 +1,158 @@
+package com.ruoyi.common.utils.spring;
+
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * spring工具类 方便在非spring管理环境中获取bean
+ * 
+ * @author ruoyi
+ */
+@Component
+public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware 
+{
+    /** Spring应用上下文环境 */
+    private static ConfigurableListableBeanFactory beanFactory;
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
+    {
+        SpringUtils.beanFactory = beanFactory;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
+    {
+        SpringUtils.applicationContext = applicationContext;
+    }
+
+    /**
+     * 获取对象
+     *
+     * @param name
+     * @return Object 一个以所给名字注册的bean的实例
+     * @throws org.springframework.beans.BeansException
+     *
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) throws BeansException
+    {
+        return (T) beanFactory.getBean(name);
+    }
+
+    /**
+     * 获取类型为requiredType的对象
+     *
+     * @param clz
+     * @return
+     * @throws org.springframework.beans.BeansException
+     *
+     */
+    public static <T> T getBean(Class<T> clz) throws BeansException
+    {
+        T result = (T) beanFactory.getBean(clz);
+        return result;
+    }
+
+    /**
+     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+     *
+     * @param name
+     * @return boolean
+     */
+    public static boolean containsBean(String name)
+    {
+        return beanFactory.containsBean(name);
+    }
+
+    /**
+     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+     *
+     * @param name
+     * @return boolean
+     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
+     *
+     */
+    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.isSingleton(name);
+    }
+
+    /**
+     * @param name
+     * @return Class 注册对象的类型
+     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
+     *
+     */
+    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.getType(name);
+    }
+
+    /**
+     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+     *
+     * @param name
+     * @return
+     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
+     *
+     */
+    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.getAliases(name);
+    }
+
+    /**
+     * 获取aop代理对象
+     * 
+     * @param invoker
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getAopProxy(T invoker)
+    {
+        return (T) AopContext.currentProxy();
+    }
+
+    /**
+     * 获取当前的环境配置,无配置返回null
+     *
+     * @return 当前的环境配置
+     */
+    public static String[] getActiveProfiles()
+    {
+        return applicationContext.getEnvironment().getActiveProfiles();
+    }
+
+    /**
+     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
+     *
+     * @return 当前的环境配置
+     */
+    public static String getActiveProfile()
+    {
+        final String[] activeProfiles = getActiveProfiles();
+        return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
+    }
+
+    /**
+     * 获取配置文件中的值
+     *
+     * @param key 配置文件的key
+     * @return 当前的配置文件的值
+     *
+     */
+    public static String getRequiredProperty(String key)
+    {
+        return applicationContext.getEnvironment().getRequiredProperty(key);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java
new file mode 100644
index 0000000..451c7dc
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java
@@ -0,0 +1,70 @@
+package com.ruoyi.common.utils.sql;
+
+import com.ruoyi.common.exception.UtilException;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * sql操作工具类
+ * 
+ * @author ruoyi
+ */
+public class SqlUtil
+{
+    /**
+     * 定义常用的 sql关键字
+     */
+    public static String SQL_REGEX = "and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
+
+    /**
+     * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
+     */
+    public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
+
+    /**
+     * 限制orderBy最大长度
+     */
+    private static final int ORDER_BY_MAX_LENGTH = 500;
+
+    /**
+     * 检查字符,防止注入绕过
+     */
+    public static String escapeOrderBySql(String value)
+    {
+        if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
+        {
+            throw new UtilException("参数不符合规范,不能进行查询");
+        }
+        if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH)
+        {
+            throw new UtilException("参数已超过最大限制,不能进行查询");
+        }
+        return value;
+    }
+
+    /**
+     * 验证 order by 语法是否符合规范
+     */
+    public static boolean isValidOrderBySql(String value)
+    {
+        return value.matches(SQL_PATTERN);
+    }
+
+    /**
+     * SQL关键字检查
+     */
+    public static void filterKeyword(String value)
+    {
+        if (StringUtils.isEmpty(value))
+        {
+            return;
+        }
+        String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
+        for (String sqlKeyword : sqlKeywords)
+        {
+            if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1)
+            {
+                throw new UtilException("参数存在SQL注入风险");
+            }
+        }
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java
new file mode 100644
index 0000000..2c84427
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java
@@ -0,0 +1,49 @@
+package com.ruoyi.common.utils.uuid;
+
+/**
+ * ID生成器工具类
+ * 
+ * @author ruoyi
+ */
+public class IdUtils
+{
+    /**
+     * 获取随机UUID
+     * 
+     * @return 随机UUID
+     */
+    public static String randomUUID()
+    {
+        return UUID.randomUUID().toString();
+    }
+
+    /**
+     * 简化的UUID,去掉了横线
+     * 
+     * @return 简化的UUID,去掉了横线
+     */
+    public static String simpleUUID()
+    {
+        return UUID.randomUUID().toString(true);
+    }
+
+    /**
+     * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID
+     * 
+     * @return 随机UUID
+     */
+    public static String fastUUID()
+    {
+        return UUID.fastUUID().toString();
+    }
+
+    /**
+     * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID
+     * 
+     * @return 简化的UUID,去掉了横线
+     */
+    public static String fastSimpleUUID()
+    {
+        return UUID.fastUUID().toString(true);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java
new file mode 100644
index 0000000..bf99611
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java
@@ -0,0 +1,86 @@
+package com.ruoyi.common.utils.uuid;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * @author ruoyi 序列生成类
+ */
+public class Seq
+{
+    // 通用序列类型
+    public static final String commSeqType = "COMMON";
+
+    // 上传序列类型
+    public static final String uploadSeqType = "UPLOAD";
+
+    // 通用接口序列数
+    private static AtomicInteger commSeq = new AtomicInteger(1);
+
+    // 上传接口序列数
+    private static AtomicInteger uploadSeq = new AtomicInteger(1);
+
+    // 机器标识
+    private static final String machineCode = "A";
+
+    /**
+     * 获取通用序列号
+     * 
+     * @return 序列值
+     */
+    public static String getId()
+    {
+        return getId(commSeqType);
+    }
+    
+    /**
+     * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串
+     * 
+     * @return 序列值
+     */
+    public static String getId(String type)
+    {
+        AtomicInteger atomicInt = commSeq;
+        if (uploadSeqType.equals(type))
+        {
+            atomicInt = uploadSeq;
+        }
+        return getId(atomicInt, 3);
+    }
+
+    /**
+     * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串
+     * 
+     * @param atomicInt 序列数
+     * @param length 数值长度
+     * @return 序列值
+     */
+    public static String getId(AtomicInteger atomicInt, int length)
+    {
+        String result = DateUtils.dateTimeNow();
+        result += machineCode;
+        result += getSeq(atomicInt, length);
+        return result;
+    }
+
+    /**
+     * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数
+     * 
+     * @return 序列值
+     */
+    private synchronized static String getSeq(AtomicInteger atomicInt, int length)
+    {
+        // 先取值再+1
+        int value = atomicInt.getAndIncrement();
+
+        // 如果更新后值>=10 的 (length)幂次方则重置为1
+        int maxSeq = (int) Math.pow(10, length);
+        if (atomicInt.get() >= maxSeq)
+        {
+            atomicInt.set(1);
+        }
+        // 转字符串,用0左补齐
+        return StringUtils.padl(value, length);
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java
new file mode 100644
index 0000000..a5585d6
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java
@@ -0,0 +1,484 @@
+package com.ruoyi.common.utils.uuid;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+import com.ruoyi.common.exception.UtilException;
+
+/**
+ * 提供通用唯一识别码(universally unique identifier)(UUID)实现
+ *
+ * @author ruoyi
+ */
+public final class UUID implements java.io.Serializable, Comparable<UUID>
+{
+    private static final long serialVersionUID = -1185015143654744140L;
+
+    /**
+     * SecureRandom 的单例
+     *
+     */
+    private static class Holder
+    {
+        static final SecureRandom numberGenerator = getSecureRandom();
+    }
+
+    /** 此UUID的最高64有效位 */
+    private final long mostSigBits;
+
+    /** 此UUID的最低64有效位 */
+    private final long leastSigBits;
+
+    /**
+     * 私有构造
+     * 
+     * @param data 数据
+     */
+    private UUID(byte[] data)
+    {
+        long msb = 0;
+        long lsb = 0;
+        assert data.length == 16 : "data must be 16 bytes in length";
+        for (int i = 0; i < 8; i++)
+        {
+            msb = (msb << 8) | (data[i] & 0xff);
+        }
+        for (int i = 8; i < 16; i++)
+        {
+            lsb = (lsb << 8) | (data[i] & 0xff);
+        }
+        this.mostSigBits = msb;
+        this.leastSigBits = lsb;
+    }
+
+    /**
+     * 使用指定的数据构造新的 UUID。
+     *
+     * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位
+     * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位
+     */
+    public UUID(long mostSigBits, long leastSigBits)
+    {
+        this.mostSigBits = mostSigBits;
+        this.leastSigBits = leastSigBits;
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。
+     * 
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID fastUUID()
+    {
+        return randomUUID(false);
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
+     * 
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID randomUUID()
+    {
+        return randomUUID(true);
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
+     * 
+     * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID randomUUID(boolean isSecure)
+    {
+        final Random ng = isSecure ? Holder.numberGenerator : getRandom();
+
+        byte[] randomBytes = new byte[16];
+        ng.nextBytes(randomBytes);
+        randomBytes[6] &= 0x0f; /* clear version */
+        randomBytes[6] |= 0x40; /* set to version 4 */
+        randomBytes[8] &= 0x3f; /* clear variant */
+        randomBytes[8] |= 0x80; /* set to IETF variant */
+        return new UUID(randomBytes);
+    }
+
+    /**
+     * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。
+     *
+     * @param name 用于构造 UUID 的字节数组。
+     *
+     * @return 根据指定数组生成的 {@code UUID}
+     */
+    public static UUID nameUUIDFromBytes(byte[] name)
+    {
+        MessageDigest md;
+        try
+        {
+            md = MessageDigest.getInstance("MD5");
+        }
+        catch (NoSuchAlgorithmException nsae)
+        {
+            throw new InternalError("MD5 not supported");
+        }
+        byte[] md5Bytes = md.digest(name);
+        md5Bytes[6] &= 0x0f; /* clear version */
+        md5Bytes[6] |= 0x30; /* set to version 3 */
+        md5Bytes[8] &= 0x3f; /* clear variant */
+        md5Bytes[8] |= 0x80; /* set to IETF variant */
+        return new UUID(md5Bytes);
+    }
+
+    /**
+     * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。
+     *
+     * @param name 指定 {@code UUID} 字符串
+     * @return 具有指定值的 {@code UUID}
+     * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常
+     *
+     */
+    public static UUID fromString(String name)
+    {
+        String[] components = name.split("-");
+        if (components.length != 5)
+        {
+            throw new IllegalArgumentException("Invalid UUID string: " + name);
+        }
+        for (int i = 0; i < 5; i++)
+        {
+            components[i] = "0x" + components[i];
+        }
+
+        long mostSigBits = Long.decode(components[0]).longValue();
+        mostSigBits <<= 16;
+        mostSigBits |= Long.decode(components[1]).longValue();
+        mostSigBits <<= 16;
+        mostSigBits |= Long.decode(components[2]).longValue();
+
+        long leastSigBits = Long.decode(components[3]).longValue();
+        leastSigBits <<= 48;
+        leastSigBits |= Long.decode(components[4]).longValue();
+
+        return new UUID(mostSigBits, leastSigBits);
+    }
+
+    /**
+     * 返回此 UUID 的 128 位值中的最低有效 64 位。
+     *
+     * @return 此 UUID 的 128 位值中的最低有效 64 位。
+     */
+    public long getLeastSignificantBits()
+    {
+        return leastSigBits;
+    }
+
+    /**
+     * 返回此 UUID 的 128 位值中的最高有效 64 位。
+     *
+     * @return 此 UUID 的 128 位值中最高有效 64 位。
+     */
+    public long getMostSignificantBits()
+    {
+        return mostSigBits;
+    }
+
+    /**
+     * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。
+     * <p>
+     * 版本号具有以下含意:
+     * <ul>
+     * <li>1 基于时间的 UUID
+     * <li>2 DCE 安全 UUID
+     * <li>3 基于名称的 UUID
+     * <li>4 随机生成的 UUID
+     * </ul>
+     *
+     * @return 此 {@code UUID} 的版本号
+     */
+    public int version()
+    {
+        // Version is bits masked by 0x000000000000F000 in MS long
+        return (int) ((mostSigBits >> 12) & 0x0f);
+    }
+
+    /**
+     * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。
+     * <p>
+     * 变体号具有以下含意:
+     * <ul>
+     * <li>0 为 NCS 向后兼容保留
+     * <li>2 <a href="http://www.ietf.org/rfc/rfc4122.txt">IETF&nbsp;RFC&nbsp;4122</a>(Leach-Salz), 用于此类
+     * <li>6 保留,微软向后兼容
+     * <li>7 保留供以后定义使用
+     * </ul>
+     *
+     * @return 此 {@code UUID} 相关联的变体号
+     */
+    public int variant()
+    {
+        // This field is composed of a varying number of bits.
+        // 0 - - Reserved for NCS backward compatibility
+        // 1 0 - The IETF aka Leach-Salz variant (used by this class)
+        // 1 1 0 Reserved, Microsoft backward compatibility
+        // 1 1 1 Reserved for future definition.
+        return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63));
+    }
+
+    /**
+     * 与此 UUID 相关联的时间戳值。
+     *
+     * <p>
+     * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。<br>
+     * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。
+     *
+     * <p>
+     * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
+     * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
+     *
+     * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。
+     */
+    public long timestamp() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return (mostSigBits & 0x0FFFL) << 48//
+                | ((mostSigBits >> 16) & 0x0FFFFL) << 32//
+                | mostSigBits >>> 32;
+    }
+
+    /**
+     * 与此 UUID 相关联的时钟序列值。
+     *
+     * <p>
+     * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。
+     * <p>
+     * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出
+     * UnsupportedOperationException。
+     *
+     * @return 此 {@code UUID} 的时钟序列
+     *
+     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
+     */
+    public int clockSequence() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48);
+    }
+
+    /**
+     * 与此 UUID 相关的节点值。
+     *
+     * <p>
+     * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。
+     * <p>
+     * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
+     * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
+     *
+     * @return 此 {@code UUID} 的节点值
+     *
+     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
+     */
+    public long node() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return leastSigBits & 0x0000FFFFFFFFFFFFL;
+    }
+
+    /**
+     * 返回此{@code UUID} 的字符串表现形式。
+     *
+     * <p>
+     * UUID 的字符串表示形式由此 BNF 描述:
+     * 
+     * <pre>
+     * {@code
+     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
+     * time_low               = 4*<hexOctet>
+     * time_mid               = 2*<hexOctet>
+     * time_high_and_version  = 2*<hexOctet>
+     * variant_and_sequence   = 2*<hexOctet>
+     * node                   = 6*<hexOctet>
+     * hexOctet               = <hexDigit><hexDigit>
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * </pre>
+     * 
+     * </blockquote>
+     *
+     * @return 此{@code UUID} 的字符串表现形式
+     * @see #toString(boolean)
+     */
+    @Override
+    public String toString()
+    {
+        return toString(false);
+    }
+
+    /**
+     * 返回此{@code UUID} 的字符串表现形式。
+     *
+     * <p>
+     * UUID 的字符串表示形式由此 BNF 描述:
+     * 
+     * <pre>
+     * {@code
+     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
+     * time_low               = 4*<hexOctet>
+     * time_mid               = 2*<hexOctet>
+     * time_high_and_version  = 2*<hexOctet>
+     * variant_and_sequence   = 2*<hexOctet>
+     * node                   = 6*<hexOctet>
+     * hexOctet               = <hexDigit><hexDigit>
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * </pre>
+     * 
+     * </blockquote>
+     *
+     * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串
+     * @return 此{@code UUID} 的字符串表现形式
+     */
+    public String toString(boolean isSimple)
+    {
+        final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36);
+        // time_low
+        builder.append(digits(mostSigBits >> 32, 8));
+        if (!isSimple)
+        {
+            builder.append('-');
+        }
+        // time_mid
+        builder.append(digits(mostSigBits >> 16, 4));
+        if (!isSimple)
+        {
+            builder.append('-');
+        }
+        // time_high_and_version
+        builder.append(digits(mostSigBits, 4));
+        if (!isSimple)
+        {
+            builder.append('-');
+        }
+        // variant_and_sequence
+        builder.append(digits(leastSigBits >> 48, 4));
+        if (!isSimple)
+        {
+            builder.append('-');
+        }
+        // node
+        builder.append(digits(leastSigBits, 12));
+
+        return builder.toString();
+    }
+
+    /**
+     * 返回此 UUID 的哈希码。
+     *
+     * @return UUID 的哈希码值。
+     */
+    @Override
+    public int hashCode()
+    {
+        long hilo = mostSigBits ^ leastSigBits;
+        return ((int) (hilo >> 32)) ^ (int) hilo;
+    }
+
+    /**
+     * 将此对象与指定对象比较。
+     * <p>
+     * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。
+     *
+     * @param obj 要与之比较的对象
+     *
+     * @return 如果对象相同,则返回 {@code true};否则返回 {@code false}
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if ((null == obj) || (obj.getClass() != UUID.class))
+        {
+            return false;
+        }
+        UUID id = (UUID) obj;
+        return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits);
+    }
+
+    // Comparison Operations
+
+    /**
+     * 将此 UUID 与指定的 UUID 比较。
+     *
+     * <p>
+     * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。
+     *
+     * @param val 与此 UUID 比较的 UUID
+     *
+     * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。
+     *
+     */
+    @Override
+    public int compareTo(UUID val)
+    {
+        // The ordering is intentionally set up so that the UUIDs
+        // can simply be numerically compared as two numbers
+        return (this.mostSigBits < val.mostSigBits ? -1 : //
+                (this.mostSigBits > val.mostSigBits ? 1 : //
+                        (this.leastSigBits < val.leastSigBits ? -1 : //
+                                (this.leastSigBits > val.leastSigBits ? 1 : //
+                                        0))));
+    }
+
+    // -------------------------------------------------------------------------------------------------------------------
+    // Private method start
+    /**
+     * 返回指定数字对应的hex值
+     * 
+     * @param val 值
+     * @param digits 位
+     * @return 值
+     */
+    private static String digits(long val, int digits)
+    {
+        long hi = 1L << (digits * 4);
+        return Long.toHexString(hi | (val & (hi - 1))).substring(1);
+    }
+
+    /**
+     * 检查是否为time-based版本UUID
+     */
+    private void checkTimeBase()
+    {
+        if (version() != 1)
+        {
+            throw new UnsupportedOperationException("Not a time-based UUID");
+        }
+    }
+
+    /**
+     * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG)
+     * 
+     * @return {@link SecureRandom}
+     */
+    public static SecureRandom getSecureRandom()
+    {
+        try
+        {
+            return SecureRandom.getInstance("SHA1PRNG");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new UtilException(e);
+        }
+    }
+
+    /**
+     * 获取随机数生成器对象<br>
+     * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。
+     * 
+     * @return {@link ThreadLocalRandom}
+     */
+    public static ThreadLocalRandom getRandom()
+    {
+        return ThreadLocalRandom.current();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java
new file mode 100644
index 0000000..cb4b408
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java
@@ -0,0 +1,27 @@
+package com.ruoyi.common.xss;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 自定义xss校验注解
+ * 
+ * @author ruoyi
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
+@Constraint(validatedBy = { XssValidator.class })
+public @interface Xss
+{
+    String message()
+
+    default "不允许任何脚本运行";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java
new file mode 100644
index 0000000..7c8e4c0
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java
@@ -0,0 +1,39 @@
+package com.ruoyi.common.xss;
+
+import com.ruoyi.common.utils.StringUtils;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 自定义xss校验注解实现
+ * 
+ * @author ruoyi
+ */
+public class XssValidator implements ConstraintValidator<Xss, String>
+{
+    private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />";
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext)
+    {
+        if (StringUtils.isBlank(value))
+        {
+            return true;
+        }
+        return !containsHtml(value);
+    }
+
+    public static boolean containsHtml(String value)
+    {
+        StringBuilder sHtml = new StringBuilder();
+        Pattern pattern = Pattern.compile(HTML_PATTERN);
+        Matcher matcher = pattern.matcher(value);
+        while (matcher.find())
+        {
+            sHtml.append(matcher.group());
+        }
+        return pattern.matcher(sHtml).matches();
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml
new file mode 100644
index 0000000..6ed5f58
--- /dev/null
+++ b/ruoyi-framework/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.8.8</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-framework</artifactId>
+
+    <description>
+        framework框架核心
+    </description>
+
+    <dependencies>
+
+        <!-- SpringBoot Web容器 -->
+         <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- SpringBoot 拦截器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- 阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-3-starter</artifactId>
+        </dependency>
+
+        <!-- 验证码 -->
+        <dependency>
+            <groupId>pro.fessional</groupId>
+            <artifactId>kaptcha</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>servlet-api</artifactId>
+                    <groupId>javax.servlet</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- 获取系统信息 -->
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+        </dependency>
+
+        <!-- 系统模块-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-system</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java
new file mode 100644
index 0000000..b2337c9
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java
@@ -0,0 +1,184 @@
+package com.ruoyi.framework.aspectj;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.annotation.DataScope;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.security.context.PermissionContextHolder;
+
+/**
+ * 数据过滤处理
+ *
+ * @author ruoyi
+ */
+@Aspect
+@Component
+public class DataScopeAspect
+{
+    /**
+     * 全部数据权限
+     */
+    public static final String DATA_SCOPE_ALL = "1";
+
+    /**
+     * 自定数据权限
+     */
+    public static final String DATA_SCOPE_CUSTOM = "2";
+
+    /**
+     * 部门数据权限
+     */
+    public static final String DATA_SCOPE_DEPT = "3";
+
+    /**
+     * 部门及以下数据权限
+     */
+    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
+
+    /**
+     * 仅本人数据权限
+     */
+    public static final String DATA_SCOPE_SELF = "5";
+
+    /**
+     * 数据权限过滤关键字
+     */
+    public static final String DATA_SCOPE = "dataScope";
+
+    @Before("@annotation(controllerDataScope)")
+    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
+    {
+        clearDataScope(point);
+        handleDataScope(point, controllerDataScope);
+    }
+
+    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
+    {
+        // 获取当前的用户
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNotNull(loginUser))
+        {
+            SysUser currentUser = loginUser.getUser();
+            // 如果是超级管理员,则不过滤数据
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
+            {
+                String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
+                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission);
+            }
+        }
+    }
+
+    /**
+     * 数据范围过滤
+     *
+     * @param joinPoint 切点
+     * @param user 用户
+     * @param deptAlias 部门别名
+     * @param userAlias 用户别名
+     * @param permission 权限字符
+     */
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
+    {
+        StringBuilder sqlString = new StringBuilder();
+        List<String> conditions = new ArrayList<String>();
+        List<String> scopeCustomIds = new ArrayList<String>();
+        user.getRoles().forEach(role -> {
+            if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
+            {
+                scopeCustomIds.add(Convert.toStr(role.getRoleId()));
+            }
+        });
+
+        for (SysRole role : user.getRoles())
+        {
+            String dataScope = role.getDataScope();
+            if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE))
+            {
+                continue;
+            }
+            if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
+            {
+                continue;
+            }
+            if (DATA_SCOPE_ALL.equals(dataScope))
+            {
+                sqlString = new StringBuilder();
+                conditions.add(dataScope);
+                break;
+            }
+            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
+            {
+                if (scopeCustomIds.size() > 1)
+                {
+                    // 多个自定数据权限使用in查询,避免多次拼接。
+                    sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds)));
+                }
+                else
+                {
+                    sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId()));
+                }
+            }
+            else if (DATA_SCOPE_DEPT.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
+            }
+            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId()));
+            }
+            else if (DATA_SCOPE_SELF.equals(dataScope))
+            {
+                if (StringUtils.isNotBlank(userAlias))
+                {
+                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
+                }
+                else
+                {
+                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
+                    sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
+                }
+            }
+            conditions.add(dataScope);
+        }
+
+        // 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据
+        if (StringUtils.isEmpty(conditions))
+        {
+            sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
+        }
+
+        if (StringUtils.isNotBlank(sqlString.toString()))
+        {
+            Object params = joinPoint.getArgs()[0];
+            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+            {
+                BaseEntity baseEntity = (BaseEntity) params;
+                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
+            }
+        }
+    }
+
+    /**
+     * 拼接权限sql前先清空params.dataScope参数防止注入
+     */
+    private void clearDataScope(final JoinPoint joinPoint)
+    {
+        Object params = joinPoint.getArgs()[0];
+        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+        {
+            BaseEntity baseEntity = (BaseEntity) params;
+            baseEntity.getParams().put(DATA_SCOPE, "");
+        }
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java
new file mode 100644
index 0000000..8c2c9f4
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java
@@ -0,0 +1,72 @@
+package com.ruoyi.framework.aspectj;
+
+import java.util.Objects;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.annotation.DataSource;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
+
+/**
+ * 多数据源处理
+ * 
+ * @author ruoyi
+ */
+@Aspect
+@Order(1)
+@Component
+public class DataSourceAspect
+{
+    protected Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
+            + "|| @within(com.ruoyi.common.annotation.DataSource)")
+    public void dsPointCut()
+    {
+
+    }
+
+    @Around("dsPointCut()")
+    public Object around(ProceedingJoinPoint point) throws Throwable
+    {
+        DataSource dataSource = getDataSource(point);
+
+        if (StringUtils.isNotNull(dataSource))
+        {
+            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
+        }
+
+        try
+        {
+            return point.proceed();
+        }
+        finally
+        {
+            // 销毁数据源 在执行方法之后
+            DynamicDataSourceContextHolder.clearDataSourceType();
+        }
+    }
+
+    /**
+     * 获取需要切换的数据源
+     */
+    public DataSource getDataSource(ProceedingJoinPoint point)
+    {
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
+        if (Objects.nonNull(dataSource))
+        {
+            return dataSource;
+        }
+
+        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
new file mode 100644
index 0000000..7f59d50
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
@@ -0,0 +1,255 @@
+package com.ruoyi.framework.aspectj;
+
+import java.util.Collection;
+import java.util.Map;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.ArrayUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.NamedThreadLocal;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.enums.BusinessStatus;
+import com.ruoyi.common.enums.HttpMethod;
+import com.ruoyi.common.filter.PropertyPreExcludeFilter;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.framework.manager.factory.AsyncFactory;
+import com.ruoyi.system.domain.SysOperLog;
+
+/**
+ * 操作日志记录处理
+ * 
+ * @author ruoyi
+ */
+@Aspect
+@Component
+public class LogAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
+
+    /** 排除敏感属性字段 */
+    public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
+
+    /** 计算操作消耗时间 */
+    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
+
+    /**
+     * 处理请求前执行
+     */
+    @Before(value = "@annotation(controllerLog)")
+    public void boBefore(JoinPoint joinPoint, Log controllerLog)
+    {
+        TIME_THREADLOCAL.set(System.currentTimeMillis());
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
+    {
+        handleLog(joinPoint, controllerLog, null, jsonResult);
+    }
+
+    /**
+     * 拦截异常操作
+     * 
+     * @param joinPoint 切点
+     * @param e 异常
+     */
+    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
+    {
+        handleLog(joinPoint, controllerLog, e, null);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
+    {
+        try
+        {
+            // 获取当前的用户
+            LoginUser loginUser = SecurityUtils.getLoginUser();
+
+            // *========数据库日志=========*//
+            SysOperLog operLog = new SysOperLog();
+            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+            // 请求的地址
+            String ip = IpUtils.getIpAddr();
+            operLog.setOperIp(ip);
+            operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
+            if (loginUser != null)
+            {
+                operLog.setOperName(loginUser.getUsername());
+                SysUser currentUser = loginUser.getUser();
+                if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept()))
+                {
+                    operLog.setDeptName(currentUser.getDept().getDeptName());
+                }
+            }
+
+            if (e != null)
+            {
+                operLog.setStatus(BusinessStatus.FAIL.ordinal());
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+            }
+            // 设置方法名称
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            operLog.setMethod(className + "." + methodName + "()");
+            // 设置请求方式
+            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+            // 处理设置注解上的参数
+            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
+            // 设置消耗时间
+            operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
+            // 保存数据库
+            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
+        }
+        catch (Exception exp)
+        {
+            // 记录本地异常日志
+            log.error("异常信息:{}", exp.getMessage());
+            exp.printStackTrace();
+        }
+        finally
+        {
+            TIME_THREADLOCAL.remove();
+        }
+    }
+
+    /**
+     * 获取注解中对方法的描述信息 用于Controller层注解
+     * 
+     * @param log 日志
+     * @param operLog 操作日志
+     * @throws Exception
+     */
+    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
+    {
+        // 设置action动作
+        operLog.setBusinessType(log.businessType().ordinal());
+        // 设置标题
+        operLog.setTitle(log.title());
+        // 设置操作人类别
+        operLog.setOperatorType(log.operatorType().ordinal());
+        // 是否需要保存request,参数和值
+        if (log.isSaveRequestData())
+        {
+            // 获取参数的信息,传入到数据库中。
+            setRequestValue(joinPoint, operLog, log.excludeParamNames());
+        }
+        // 是否需要保存response,参数和值
+        if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
+        {
+            operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
+        }
+    }
+
+    /**
+     * 获取请求的参数,放到log中
+     * 
+     * @param operLog 操作日志
+     * @throws Exception 异常
+     */
+    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception
+    {
+        Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
+        String requestMethod = operLog.getRequestMethod();
+        if (StringUtils.isEmpty(paramsMap)
+                && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)))
+        {
+            String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
+            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+        }
+        else
+        {
+            operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
+        }
+    }
+
+    /**
+     * 参数拼装
+     */
+    private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
+    {
+        String params = "";
+        if (paramsArray != null && paramsArray.length > 0)
+        {
+            for (Object o : paramsArray)
+            {
+                if (StringUtils.isNotNull(o) && !isFilterObject(o))
+                {
+                    try
+                    {
+                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
+                        params += jsonObj.toString() + " ";
+                    }
+                    catch (Exception e)
+                    {
+                    }
+                }
+            }
+        }
+        return params.trim();
+    }
+
+    /**
+     * 忽略敏感属性
+     */
+    public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames)
+    {
+        return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
+    }
+
+    /**
+     * 判断是否需要过滤的对象。
+     * 
+     * @param o 对象信息。
+     * @return 如果是需要过滤的对象,则返回true;否则返回false。
+     */
+    @SuppressWarnings("rawtypes")
+    public boolean isFilterObject(final Object o)
+    {
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray())
+        {
+            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
+        }
+        else if (Collection.class.isAssignableFrom(clazz))
+        {
+            Collection collection = (Collection) o;
+            for (Object value : collection)
+            {
+                return value instanceof MultipartFile;
+            }
+        }
+        else if (Map.class.isAssignableFrom(clazz))
+        {
+            Map map = (Map) o;
+            for (Object value : map.entrySet())
+            {
+                Map.Entry entry = (Map.Entry) value;
+                return entry.getValue() instanceof MultipartFile;
+            }
+        }
+        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
+                || o instanceof BindingResult;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
new file mode 100644
index 0000000..b720bc1
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
@@ -0,0 +1,89 @@
+package com.ruoyi.framework.aspectj;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.annotation.RateLimiter;
+import com.ruoyi.common.enums.LimitType;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+
+/**
+ * 限流处理
+ *
+ * @author ruoyi
+ */
+@Aspect
+@Component
+public class RateLimiterAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
+
+    private RedisTemplate<Object, Object> redisTemplate;
+
+    private RedisScript<Long> limitScript;
+
+    @Autowired
+    public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
+    {
+        this.redisTemplate = redisTemplate;
+    }
+
+    @Autowired
+    public void setLimitScript(RedisScript<Long> limitScript)
+    {
+        this.limitScript = limitScript;
+    }
+
+    @Before("@annotation(rateLimiter)")
+    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
+    {
+        int time = rateLimiter.time();
+        int count = rateLimiter.count();
+
+        String combineKey = getCombineKey(rateLimiter, point);
+        List<Object> keys = Collections.singletonList(combineKey);
+        try
+        {
+            Long number = redisTemplate.execute(limitScript, keys, count, time);
+            if (StringUtils.isNull(number) || number.intValue() > count)
+            {
+                throw new ServiceException("访问过于频繁,请稍候再试");
+            }
+            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);
+        }
+        catch (ServiceException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("服务器限流异常,请稍候再试");
+        }
+    }
+
+    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
+    {
+        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
+        if (rateLimiter.limitType() == LimitType.IP)
+        {
+            stringBuffer.append(IpUtils.getIpAddr()).append("-");
+        }
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        Class<?> targetClass = method.getDeclaringClass();
+        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
+        return stringBuffer.toString();
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java
new file mode 100644
index 0000000..1d4dc1f
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java
@@ -0,0 +1,30 @@
+package com.ruoyi.framework.config;
+
+import java.util.TimeZone;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+/**
+ * 程序注解配置
+ *
+ * @author ruoyi
+ */
+@Configuration
+// 表示通过aop框架暴露该代理对象,AopContext能够访问
+@EnableAspectJAutoProxy(exposeProxy = true)
+// 指定要扫描的Mapper类的包的路径
+@MapperScan("com.ruoyi.**.mapper")
+public class ApplicationConfig
+{
+    /**
+     * 时区配置
+     */
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
+    {
+        return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java
new file mode 100644
index 0000000..43e78ae
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java
@@ -0,0 +1,83 @@
+package com.ruoyi.framework.config;
+
+import java.util.Properties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import com.google.code.kaptcha.impl.DefaultKaptcha;
+import com.google.code.kaptcha.util.Config;
+import static com.google.code.kaptcha.Constants.*;
+
+/**
+ * 验证码配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class CaptchaConfig
+{
+    @Bean(name = "captchaProducer")
+    public DefaultKaptcha getKaptchaBean()
+    {
+        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+        Properties properties = new Properties();
+        // 是否有边框 默认为true 我们可以自己设置yes,no
+        properties.setProperty(KAPTCHA_BORDER, "yes");
+        // 验证码文本字符颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
+        // 验证码图片宽度 默认为200
+        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
+        // 验证码图片高度 默认为50
+        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
+        // 验证码文本字符大小 默认为40
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
+        // KAPTCHA_SESSION_KEY
+        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
+        // 验证码文本字符长度 默认为5
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
+        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
+        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
+        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
+        Config config = new Config(properties);
+        defaultKaptcha.setConfig(config);
+        return defaultKaptcha;
+    }
+
+    @Bean(name = "captchaProducerMath")
+    public DefaultKaptcha getKaptchaBeanMath()
+    {
+        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+        Properties properties = new Properties();
+        // 是否有边框 默认为true 我们可以自己设置yes,no
+        properties.setProperty(KAPTCHA_BORDER, "yes");
+        // 边框颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
+        // 验证码文本字符颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
+        // 验证码图片宽度 默认为200
+        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
+        // 验证码图片高度 默认为50
+        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
+        // 验证码文本字符大小 默认为40
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
+        // KAPTCHA_SESSION_KEY
+        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
+        // 验证码文本生成器
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator");
+        // 验证码文本字符间距 默认为2
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
+        // 验证码文本字符长度 默认为5
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
+        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
+        // 验证码噪点颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
+        // 干扰实现类
+        properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
+        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
+        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
+        Config config = new Config(properties);
+        defaultKaptcha.setConfig(config);
+        return defaultKaptcha;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java
new file mode 100644
index 0000000..d9cb8c5
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java
@@ -0,0 +1,126 @@
+package com.ruoyi.framework.config;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.sql.DataSource;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
+import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
+import com.alibaba.druid.util.Utils;
+import com.ruoyi.common.enums.DataSourceType;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.framework.config.properties.DruidProperties;
+import com.ruoyi.framework.datasource.DynamicDataSource;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+
+/**
+ * druid 配置多数据源
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class DruidConfig
+{
+    @Bean
+    @ConfigurationProperties("spring.datasource.druid.master")
+    public DataSource masterDataSource(DruidProperties druidProperties)
+    {
+        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
+        return druidProperties.dataSource(dataSource);
+    }
+
+    @Bean
+    @ConfigurationProperties("spring.datasource.druid.slave")
+    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
+    public DataSource slaveDataSource(DruidProperties druidProperties)
+    {
+        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
+        return druidProperties.dataSource(dataSource);
+    }
+
+    @Bean(name = "dynamicDataSource")
+    @Primary
+    public DynamicDataSource dataSource(DataSource masterDataSource)
+    {
+        Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
+        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
+        return new DynamicDataSource(masterDataSource, targetDataSources);
+    }
+    
+    /**
+     * 设置数据源
+     * 
+     * @param targetDataSources 备选数据源集合
+     * @param sourceName 数据源名称
+     * @param beanName bean名称
+     */
+    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
+    {
+        try
+        {
+            DataSource dataSource = SpringUtils.getBean(beanName);
+            targetDataSources.put(sourceName, dataSource);
+        }
+        catch (Exception e)
+        {
+        }
+    }
+
+    /**
+     * 去除监控页面底部的广告
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
+    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
+    {
+        // 获取web监控页面的参数
+        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+        // 提取common.js的配置路径
+        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+        final String filePath = "support/http/resources/js/common.js";
+        // 创建filter进行过滤
+        Filter filter = new Filter()
+        {
+            @Override
+            public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException
+            {
+            }
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+                    throws IOException, ServletException
+            {
+                chain.doFilter(request, response);
+                // 重置缓冲区,响应头不会被重置
+                response.resetBuffer();
+                // 获取common.js
+                String text = Utils.readFromResource(filePath);
+                // 正则替换banner, 除去底部的广告信息
+                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
+                text = text.replaceAll("powered.*?shrek.wang</a>", "");
+                response.getWriter().write(text);
+            }
+            @Override
+            public void destroy()
+            {
+            }
+        };
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
+        registrationBean.setFilter(filter);
+        registrationBean.addUrlPatterns(commonJsPattern);
+        return registrationBean;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java
new file mode 100644
index 0000000..4adbb7f
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java
@@ -0,0 +1,52 @@
+package com.ruoyi.framework.config;
+
+import java.nio.charset.Charset;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.filter.Filter;
+import com.ruoyi.common.constant.Constants;
+
+/**
+ * Redis使用FastJson序列化
+ * 
+ * @author ruoyi
+ */
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
+{
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
+
+    private Class<T> clazz;
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz)
+    {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException
+    {
+        if (t == null)
+        {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException
+    {
+        if (bytes == null || bytes.length <= 0)
+        {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java
new file mode 100644
index 0000000..fad6bb8
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java
@@ -0,0 +1,58 @@
+package com.ruoyi.framework.config;
+
+import java.util.HashMap;
+import java.util.Map;
+import jakarta.servlet.DispatcherType;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import com.ruoyi.common.filter.RepeatableFilter;
+import com.ruoyi.common.filter.XssFilter;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * Filter配置
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class FilterConfig
+{
+    @Value("${xss.excludes}")
+    private String excludes;
+
+    @Value("${xss.urlPatterns}")
+    private String urlPatterns;
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
+    public FilterRegistrationBean xssFilterRegistration()
+    {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setDispatcherTypes(DispatcherType.REQUEST);
+        registration.setFilter(new XssFilter());
+        registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
+        registration.setName("xssFilter");
+        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
+        Map<String, String> initParameters = new HashMap<String, String>();
+        initParameters.put("excludes", excludes);
+        registration.setInitParameters(initParameters);
+        return registration;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    public FilterRegistrationBean someFilterRegistration()
+    {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setFilter(new RepeatableFilter());
+        registration.addUrlPatterns("/*");
+        registration.setName("repeatableFilter");
+        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
+        return registration;
+    }
+
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java
new file mode 100644
index 0000000..163fd01
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java
@@ -0,0 +1,43 @@
+package com.ruoyi.framework.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.LocaleResolver;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
+import org.springframework.web.servlet.i18n.SessionLocaleResolver;
+import com.ruoyi.common.constant.Constants;
+
+/**
+ * 资源文件配置加载
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class I18nConfig implements WebMvcConfigurer
+{
+    @Bean
+    public LocaleResolver localeResolver()
+    {
+        SessionLocaleResolver slr = new SessionLocaleResolver();
+        // 默认语言
+        slr.setDefaultLocale(Constants.DEFAULT_LOCALE);
+        return slr;
+    }
+
+    @Bean
+    public LocaleChangeInterceptor localeChangeInterceptor()
+    {
+        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
+        // 参数名
+        lci.setParamName("lang");
+        return lci;
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry)
+    {
+        registry.addInterceptor(localeChangeInterceptor());
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java
new file mode 100644
index 0000000..7f8e1d5
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java
@@ -0,0 +1,68 @@
+package com.ruoyi.framework.config;
+
+import java.util.Random;
+import com.google.code.kaptcha.text.impl.DefaultTextCreator;
+
+/**
+ * 验证码文本生成器
+ *
+ * @author ruoyi
+ */
+public class KaptchaTextCreator extends DefaultTextCreator
+{
+    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
+
+    @Override
+    public String getText()
+    {
+        Integer result = 0;
+        Random random = new Random();
+        int x = random.nextInt(10);
+        int y = random.nextInt(10);
+        StringBuilder suChinese = new StringBuilder();
+        int randomoperands = random.nextInt(3);
+        if (randomoperands == 0)
+        {
+            result = x * y;
+            suChinese.append(CNUMBERS[x]);
+            suChinese.append("*");
+            suChinese.append(CNUMBERS[y]);
+        }
+        else if (randomoperands == 1)
+        {
+            if ((x != 0) && y % x == 0)
+            {
+                result = y / x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("/");
+                suChinese.append(CNUMBERS[x]);
+            }
+            else
+            {
+                result = x + y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("+");
+                suChinese.append(CNUMBERS[y]);
+            }
+        }
+        else
+        {
+            if (x >= y)
+            {
+                result = x - y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[y]);
+            }
+            else
+            {
+                result = y - x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[x]);
+            }
+        }
+        suChinese.append("=?@" + result);
+        return suChinese.toString();
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java
new file mode 100644
index 0000000..057c941
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java
@@ -0,0 +1,132 @@
+package com.ruoyi.framework.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import javax.sql.DataSource;
+import org.apache.ibatis.io.VFS;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.util.ClassUtils;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * Mybatis支持*匹配扫描包
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class MyBatisConfig
+{
+    @Autowired
+    private Environment env;
+
+    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
+
+    public static String setTypeAliasesPackage(String typeAliasesPackage)
+    {
+        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
+        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
+        List<String> allResult = new ArrayList<String>();
+        try
+        {
+            for (String aliasesPackage : typeAliasesPackage.split(","))
+            {
+                List<String> result = new ArrayList<String>();
+                aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+                        + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
+                Resource[] resources = resolver.getResources(aliasesPackage);
+                if (resources != null && resources.length > 0)
+                {
+                    MetadataReader metadataReader = null;
+                    for (Resource resource : resources)
+                    {
+                        if (resource.isReadable())
+                        {
+                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
+                            try
+                            {
+                                result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
+                            }
+                            catch (ClassNotFoundException e)
+                            {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                }
+                if (result.size() > 0)
+                {
+                    HashSet<String> hashResult = new HashSet<String>(result);
+                    allResult.addAll(hashResult);
+                }
+            }
+            if (allResult.size() > 0)
+            {
+                typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
+            }
+            else
+            {
+                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
+            }
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+        return typeAliasesPackage;
+    }
+
+    public Resource[] resolveMapperLocations(String[] mapperLocations)
+    {
+        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
+        List<Resource> resources = new ArrayList<Resource>();
+        if (mapperLocations != null)
+        {
+            for (String mapperLocation : mapperLocations)
+            {
+                try
+                {
+                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
+                    resources.addAll(Arrays.asList(mappers));
+                }
+                catch (IOException e)
+                {
+                    // ignore
+                }
+            }
+        }
+        return resources.toArray(new Resource[resources.size()]);
+    }
+
+    @Bean
+    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
+    {
+        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
+        String mapperLocations = env.getProperty("mybatis.mapperLocations");
+        String configLocation = env.getProperty("mybatis.configLocation");
+        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
+        VFS.addImplClass(SpringBootVFS.class);
+
+        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
+        sessionFactory.setDataSource(dataSource);
+        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
+        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
+        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
+        return sessionFactory.getObject();
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
new file mode 100644
index 0000000..3453237
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
@@ -0,0 +1,70 @@
+package com.ruoyi.framework.config;
+
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * redis配置
+ * 
+ * @author ruoyi
+ */
+@SuppressWarnings("deprecation")
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport
+{
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
+    {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    @Bean
+    public DefaultRedisScript<Long> limitScript()
+    {
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+        redisScript.setScriptText(limitScriptText());
+        redisScript.setResultType(Long.class);
+        return redisScript;
+    }
+
+    /**
+     * 限流脚本
+     */
+    private String limitScriptText()
+    {
+        return "local key = KEYS[1]\n" +
+                "local count = tonumber(ARGV[1])\n" +
+                "local time = tonumber(ARGV[2])\n" +
+                "local current = redis.call('get', key);\n" +
+                "if current and tonumber(current) > count then\n" +
+                "    return tonumber(current);\n" +
+                "end\n" +
+                "current = redis.call('incr', key)\n" +
+                "if tonumber(current) == 1 then\n" +
+                "    redis.call('expire', key, time)\n" +
+                "end\n" +
+                "return tonumber(current);";
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
new file mode 100644
index 0000000..74fb93e
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
@@ -0,0 +1,73 @@
+package com.ruoyi.framework.config;
+
+import java.util.concurrent.TimeUnit;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.CacheControl;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
+
+/**
+ * 通用配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class ResourcesConfig implements WebMvcConfigurer
+{
+    @Autowired
+    private RepeatSubmitInterceptor repeatSubmitInterceptor;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry)
+    {
+        /** 本地文件上传路径 */
+        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
+                .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
+
+        /** swagger配置 */
+        registry.addResourceHandler("/swagger-ui/**")
+                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
+                .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());;
+    }
+
+    /**
+     * 自定义拦截规则
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry)
+    {
+        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+    }
+
+    /**
+     * 跨域配置
+     */
+    @Bean
+    public CorsFilter corsFilter()
+    {
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        // 设置访问源地址
+        config.addAllowedOriginPattern("*");
+        // 设置访问源请求头
+        config.addAllowedHeader("*");
+        // 设置访问源请求方法
+        config.addAllowedMethod("*");
+        // 有效期 1800秒
+        config.setMaxAge(1800L);
+        // 添加映射路径,拦截一切请求
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", config);
+        // 返回新的CorsFilter
+        return new CorsFilter(source);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
new file mode 100644
index 0000000..330039f
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -0,0 +1,139 @@
+package com.ruoyi.framework.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.web.filter.CorsFilter;
+import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
+import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
+import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
+import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
+
+/**
+ * spring security配置
+ * 
+ * @author ruoyi
+ */
+@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
+@Configuration
+public class SecurityConfig
+{
+    /**
+     * 自定义用户认证逻辑
+     */
+    @Autowired
+    private UserDetailsService userDetailsService;
+    
+    /**
+     * 认证失败处理类
+     */
+    @Autowired
+    private AuthenticationEntryPointImpl unauthorizedHandler;
+
+    /**
+     * 退出处理类
+     */
+    @Autowired
+    private LogoutSuccessHandlerImpl logoutSuccessHandler;
+
+    /**
+     * token认证过滤器
+     */
+    @Autowired
+    private JwtAuthenticationTokenFilter authenticationTokenFilter;
+    
+    /**
+     * 跨域过滤器
+     */
+    @Autowired
+    private CorsFilter corsFilter;
+
+    /**
+     * 允许匿名访问的地址
+     */
+    @Autowired
+    private PermitAllUrlProperties permitAllUrl;
+
+    /**
+     * 身份验证实现
+     */
+    @Bean
+    public AuthenticationManager authenticationManager()
+    {
+        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
+        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
+        daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
+        return new ProviderManager(daoAuthenticationProvider);
+    }
+
+    /**
+     * anyRequest          |   匹配所有请求路径
+     * access              |   SpringEl表达式结果为true时可以访问
+     * anonymous           |   匿名可以访问
+     * denyAll             |   用户不能访问
+     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
+     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
+     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
+     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
+     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
+     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
+     * permitAll           |   用户可以任意访问
+     * rememberMe          |   允许通过remember-me登录的用户访问
+     * authenticated       |   用户登录后可访问
+     */
+    @Bean
+    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
+    {
+        return httpSecurity
+            // CSRF禁用,因为不使用session
+            .csrf(csrf -> csrf.disable())
+            // 禁用HTTP响应标头
+            .headers((headersCustomizer) -> {
+                headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
+            })
+            // 认证失败处理类
+            .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
+            // 基于token,所以不需要session
+            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+            // 注解标记允许匿名访问的url
+            .authorizeHttpRequests((requests) -> {
+                permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
+                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
+                requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
+                    // 静态资源,可匿名访问
+                    .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
+                    .requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
+                    // 除上面外的所有请求全部需要鉴权认证
+                    .anyRequest().authenticated();
+            })
+            // 添加Logout filter
+            .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
+            // 添加JWT filter
+            .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
+            // 添加CORS filter
+            .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
+            .addFilterBefore(corsFilter, LogoutFilter.class)
+            .build();
+    }
+
+    /**
+     * 强散列哈希加密实现
+     */
+    @Bean
+    public BCryptPasswordEncoder bCryptPasswordEncoder()
+    {
+        return new BCryptPasswordEncoder();
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java
new file mode 100644
index 0000000..cd8fb09
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java
@@ -0,0 +1,32 @@
+package com.ruoyi.framework.config;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.ServletUtils;
+
+/**
+ * 服务相关配置
+ * 
+ * @author ruoyi
+ */
+@Component
+public class ServerConfig
+{
+    /**
+     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
+     * 
+     * @return 服务地址
+     */
+    public String getUrl()
+    {
+        HttpServletRequest request = ServletUtils.getRequest();
+        return getDomain(request);
+    }
+
+    public static String getDomain(HttpServletRequest request)
+    {
+        StringBuffer url = request.getRequestURL();
+        String contextPath = request.getServletContext().getContextPath();
+        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java
new file mode 100644
index 0000000..7840141
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java
@@ -0,0 +1,63 @@
+package com.ruoyi.framework.config;
+
+import com.ruoyi.common.utils.Threads;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ *
+ * @author ruoyi
+ **/
+@Configuration
+public class ThreadPoolConfig
+{
+    // 核心线程池大小
+    private int corePoolSize = 50;
+
+    // 最大可创建的线程数
+    private int maxPoolSize = 200;
+
+    // 队列最大长度
+    private int queueCapacity = 1000;
+
+    // 线程池维护线程所允许的空闲时间
+    private int keepAliveSeconds = 300;
+
+    @Bean(name = "threadPoolTaskExecutor")
+    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
+    {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setCorePoolSize(corePoolSize);
+        executor.setQueueCapacity(queueCapacity);
+        executor.setKeepAliveSeconds(keepAliveSeconds);
+        // 线程池对拒绝任务(无线程可用)的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
+    /**
+     * 执行周期性或定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    protected ScheduledExecutorService scheduledExecutorService()
+    {
+        return new ScheduledThreadPoolExecutor(corePoolSize,
+                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
+                new ThreadPoolExecutor.CallerRunsPolicy())
+        {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t)
+            {
+                super.afterExecute(r, t);
+                Threads.printException(r, t);
+            }
+        };
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java
new file mode 100644
index 0000000..c8a5c8a
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java
@@ -0,0 +1,89 @@
+package com.ruoyi.framework.config.properties;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import com.alibaba.druid.pool.DruidDataSource;
+
+/**
+ * druid 配置属性
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class DruidProperties
+{
+    @Value("${spring.datasource.druid.initialSize}")
+    private int initialSize;
+
+    @Value("${spring.datasource.druid.minIdle}")
+    private int minIdle;
+
+    @Value("${spring.datasource.druid.maxActive}")
+    private int maxActive;
+
+    @Value("${spring.datasource.druid.maxWait}")
+    private int maxWait;
+
+    @Value("${spring.datasource.druid.connectTimeout}")
+    private int connectTimeout;
+
+    @Value("${spring.datasource.druid.socketTimeout}")
+    private int socketTimeout;
+
+    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
+    private int timeBetweenEvictionRunsMillis;
+
+    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
+    private int minEvictableIdleTimeMillis;
+
+    @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
+    private int maxEvictableIdleTimeMillis;
+
+    @Value("${spring.datasource.druid.validationQuery}")
+    private String validationQuery;
+
+    @Value("${spring.datasource.druid.testWhileIdle}")
+    private boolean testWhileIdle;
+
+    @Value("${spring.datasource.druid.testOnBorrow}")
+    private boolean testOnBorrow;
+
+    @Value("${spring.datasource.druid.testOnReturn}")
+    private boolean testOnReturn;
+
+    public DruidDataSource dataSource(DruidDataSource datasource)
+    {
+        /** 配置初始化大小、最小、最大 */
+        datasource.setInitialSize(initialSize);
+        datasource.setMaxActive(maxActive);
+        datasource.setMinIdle(minIdle);
+
+        /** 配置获取连接等待超时的时间 */
+        datasource.setMaxWait(maxWait);
+        
+        /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */
+        datasource.setConnectTimeout(connectTimeout);
+        
+        /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */
+        datasource.setSocketTimeout(socketTimeout);
+
+        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
+        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+
+        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
+        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
+        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
+
+        /**
+         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
+         */
+        datasource.setValidationQuery(validationQuery);
+        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
+        datasource.setTestWhileIdle(testWhileIdle);
+        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnBorrow(testOnBorrow);
+        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnReturn(testOnReturn);
+        return datasource;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java
new file mode 100644
index 0000000..277ba3b
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java
@@ -0,0 +1,73 @@
+package com.ruoyi.framework.config.properties;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.RegExUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import com.ruoyi.common.annotation.Anonymous;
+
+/**
+ * 设置Anonymous注解允许匿名访问的url
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
+{
+    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
+
+    private ApplicationContext applicationContext;
+
+    private List<String> urls = new ArrayList<>();
+
+    public String ASTERISK = "*";
+
+    @Override
+    public void afterPropertiesSet()
+    {
+        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
+        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
+
+        map.keySet().forEach(info -> {
+            HandlerMethod handlerMethod = map.get(info);
+
+            // 获取方法上边的注解 替代path variable 为 *
+            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
+            Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues()) //
+                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
+
+            // 获取类上边的注解, 替代path variable 为 *
+            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
+            Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues())
+                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
+        });
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException
+    {
+        this.applicationContext = context;
+    }
+
+    public List<String> getUrls()
+    {
+        return urls;
+    }
+
+    public void setUrls(List<String> urls)
+    {
+        this.urls = urls;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java
new file mode 100644
index 0000000..e70b8cf
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java
@@ -0,0 +1,26 @@
+package com.ruoyi.framework.datasource;
+
+import java.util.Map;
+import javax.sql.DataSource;
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+/**
+ * 动态数据源
+ * 
+ * @author ruoyi
+ */
+public class DynamicDataSource extends AbstractRoutingDataSource
+{
+    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
+    {
+        super.setDefaultTargetDataSource(defaultTargetDataSource);
+        super.setTargetDataSources(targetDataSources);
+        super.afterPropertiesSet();
+    }
+
+    @Override
+    protected Object determineCurrentLookupKey()
+    {
+        return DynamicDataSourceContextHolder.getDataSourceType();
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java
new file mode 100644
index 0000000..9770af6
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java
@@ -0,0 +1,45 @@
+package com.ruoyi.framework.datasource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 数据源切换处理
+ * 
+ * @author ruoyi
+ */
+public class DynamicDataSourceContextHolder
+{
+    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
+
+    /**
+     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
+     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
+     */
+    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
+
+    /**
+     * 设置数据源的变量
+     */
+    public static void setDataSourceType(String dsType)
+    {
+        log.info("切换到{}数据源", dsType);
+        CONTEXT_HOLDER.set(dsType);
+    }
+
+    /**
+     * 获得数据源的变量
+     */
+    public static String getDataSourceType()
+    {
+        return CONTEXT_HOLDER.get();
+    }
+
+    /**
+     * 清空数据源变量
+     */
+    public static void clearDataSourceType()
+    {
+        CONTEXT_HOLDER.remove();
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java
new file mode 100644
index 0000000..4e8d20f
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java
@@ -0,0 +1,56 @@
+package com.ruoyi.framework.interceptor;
+
+import java.lang.reflect.Method;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.ServletUtils;
+
+/**
+ * 防止重复提交拦截器
+ *
+ * @author ruoyi
+ */
+@Component
+public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
+{
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
+    {
+        if (handler instanceof HandlerMethod)
+        {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            Method method = handlerMethod.getMethod();
+            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
+            if (annotation != null)
+            {
+                if (this.isRepeatSubmit(request, annotation))
+                {
+                    AjaxResult ajaxResult = AjaxResult.error(annotation.message());
+                    ServletUtils.renderString(response, JSON.toJSONString(ajaxResult));
+                    return false;
+                }
+            }
+            return true;
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    /**
+     * 验证是否重复提交由子类实现具体的防重复提交的规则
+     *
+     * @param request 请求信息
+     * @param annotation 防重复注解参数
+     * @return 结果
+     * @throws Exception
+     */
+    public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java
new file mode 100644
index 0000000..c93df50
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java
@@ -0,0 +1,110 @@
+package com.ruoyi.framework.interceptor.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.http.HttpHelper;
+import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
+
+/**
+ * 判断请求url和数据是否和上一次相同,
+ * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
+{
+    public final String REPEAT_PARAMS = "repeatParams";
+
+    public final String REPEAT_TIME = "repeatTime";
+
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation)
+    {
+        String nowParams = "";
+        if (request instanceof RepeatedlyRequestWrapper)
+        {
+            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
+            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
+        }
+
+        // body参数为空,获取Parameter的数据
+        if (StringUtils.isEmpty(nowParams))
+        {
+            nowParams = JSON.toJSONString(request.getParameterMap());
+        }
+        Map<String, Object> nowDataMap = new HashMap<String, Object>();
+        nowDataMap.put(REPEAT_PARAMS, nowParams);
+        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
+
+        // 请求地址(作为存放cache的key值)
+        String url = request.getRequestURI();
+
+        // 唯一值(没有消息头则使用请求地址)
+        String submitKey = StringUtils.trimToEmpty(request.getHeader(header));
+
+        // 唯一标识(指定key + url + 消息头)
+        String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
+
+        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
+        if (sessionObj != null)
+        {
+            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
+            if (sessionMap.containsKey(url))
+            {
+                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
+                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval()))
+                {
+                    return true;
+                }
+            }
+        }
+        Map<String, Object> cacheMap = new HashMap<String, Object>();
+        cacheMap.put(url, nowDataMap);
+        redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);
+        return false;
+    }
+
+    /**
+     * 判断参数是否相同
+     */
+    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
+    {
+        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
+        String preParams = (String) preMap.get(REPEAT_PARAMS);
+        return nowParams.equals(preParams);
+    }
+
+    /**
+     * 判断两次间隔时间
+     */
+    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)
+    {
+        long time1 = (Long) nowMap.get(REPEAT_TIME);
+        long time2 = (Long) preMap.get(REPEAT_TIME);
+        if ((time1 - time2) < interval)
+        {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java
new file mode 100644
index 0000000..7387a02
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java
@@ -0,0 +1,55 @@
+package com.ruoyi.framework.manager;
+
+import java.util.TimerTask;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import com.ruoyi.common.utils.Threads;
+import com.ruoyi.common.utils.spring.SpringUtils;
+
+/**
+ * 异步任务管理器
+ * 
+ * @author ruoyi
+ */
+public class AsyncManager
+{
+    /**
+     * 操作延迟10毫秒
+     */
+    private final int OPERATE_DELAY_TIME = 10;
+
+    /**
+     * 异步操作任务调度线程池
+     */
+    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
+
+    /**
+     * 单例模式
+     */
+    private AsyncManager(){}
+
+    private static AsyncManager me = new AsyncManager();
+
+    public static AsyncManager me()
+    {
+        return me;
+    }
+
+    /**
+     * 执行任务
+     * 
+     * @param task 任务
+     */
+    public void execute(TimerTask task)
+    {
+        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * 停止任务线程池
+     */
+    public void shutdown()
+    {
+        Threads.shutdownAndAwaitTermination(executor);
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java
new file mode 100644
index 0000000..095b865
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java
@@ -0,0 +1,39 @@
+package com.ruoyi.framework.manager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import jakarta.annotation.PreDestroy;
+
+/**
+ * 确保应用退出时能关闭后台线程
+ *
+ * @author ruoyi
+ */
+@Component
+public class ShutdownManager
+{
+    private static final Logger logger = LoggerFactory.getLogger("sys-user");
+
+    @PreDestroy
+    public void destroy()
+    {
+        shutdownAsyncManager();
+    }
+
+    /**
+     * 停止异步执行任务
+     */
+    private void shutdownAsyncManager()
+    {
+        try
+        {
+            logger.info("====关闭后台任务任务线程池====");
+            AsyncManager.me().shutdown();
+        }
+        catch (Exception e)
+        {
+            logger.error(e.getMessage(), e);
+        }
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java
new file mode 100644
index 0000000..267e305
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java
@@ -0,0 +1,102 @@
+package com.ruoyi.framework.manager.factory;
+
+import java.util.TimerTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.utils.LogUtils;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.AddressUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.system.domain.SysLogininfor;
+import com.ruoyi.system.domain.SysOperLog;
+import com.ruoyi.system.service.ISysLogininforService;
+import com.ruoyi.system.service.ISysOperLogService;
+import eu.bitwalker.useragentutils.UserAgent;
+
+/**
+ * 异步工厂(产生任务用)
+ * 
+ * @author ruoyi
+ */
+public class AsyncFactory
+{
+    private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
+
+    /**
+     * 记录登录信息
+     * 
+     * @param username 用户名
+     * @param status 状态
+     * @param message 消息
+     * @param args 列表
+     * @return 任务task
+     */
+    public static TimerTask recordLogininfor(final String username, final String status, final String message,
+            final Object... args)
+    {
+        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
+        final String ip = IpUtils.getIpAddr();
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                String address = AddressUtils.getRealAddressByIP(ip);
+                StringBuilder s = new StringBuilder();
+                s.append(LogUtils.getBlock(ip));
+                s.append(address);
+                s.append(LogUtils.getBlock(username));
+                s.append(LogUtils.getBlock(status));
+                s.append(LogUtils.getBlock(message));
+                // 打印信息到日志
+                sys_user_logger.info(s.toString(), args);
+                // 获取客户端操作系统
+                String os = userAgent.getOperatingSystem().getName();
+                // 获取客户端浏览器
+                String browser = userAgent.getBrowser().getName();
+                // 封装对象
+                SysLogininfor logininfor = new SysLogininfor();
+                logininfor.setUserName(username);
+                logininfor.setIpaddr(ip);
+                logininfor.setLoginLocation(address);
+                logininfor.setBrowser(browser);
+                logininfor.setOs(os);
+                logininfor.setMsg(message);
+                // 日志状态
+                if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
+                {
+                    logininfor.setStatus(Constants.SUCCESS);
+                }
+                else if (Constants.LOGIN_FAIL.equals(status))
+                {
+                    logininfor.setStatus(Constants.FAIL);
+                }
+                // 插入数据
+                SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
+            }
+        };
+    }
+
+    /**
+     * 操作日志记录
+     * 
+     * @param operLog 操作日志信息
+     * @return 任务task
+     */
+    public static TimerTask recordOper(final SysOperLog operLog)
+    {
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                // 远程查询操作地点
+                operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
+                SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);
+            }
+        };
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java
new file mode 100644
index 0000000..6c776ce
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java
@@ -0,0 +1,28 @@
+package com.ruoyi.framework.security.context;
+
+import org.springframework.security.core.Authentication;
+
+/**
+ * 身份验证信息
+ * 
+ * @author ruoyi
+ */
+public class AuthenticationContextHolder
+{
+    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();
+
+    public static Authentication getContext()
+    {
+        return contextHolder.get();
+    }
+
+    public static void setContext(Authentication context)
+    {
+        contextHolder.set(context);
+    }
+
+    public static void clearContext()
+    {
+        contextHolder.remove();
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java
new file mode 100644
index 0000000..5472f3d
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java
@@ -0,0 +1,27 @@
+package com.ruoyi.framework.security.context;
+
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import com.ruoyi.common.core.text.Convert;
+
+/**
+ * 权限信息
+ * 
+ * @author ruoyi
+ */
+public class PermissionContextHolder
+{
+    private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
+
+    public static void setContext(String permission)
+    {
+        RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
+                RequestAttributes.SCOPE_REQUEST);
+    }
+
+    public static String getContext()
+    {
+        return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,
+                RequestAttributes.SCOPE_REQUEST));
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java
new file mode 100644
index 0000000..9d2f494
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java
@@ -0,0 +1,44 @@
+package com.ruoyi.framework.security.filter;
+
+import java.io.IOException;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.web.service.TokenService;
+
+/**
+ * token过滤器 验证token有效性
+ * 
+ * @author ruoyi
+ */
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
+{
+    @Autowired
+    private TokenService tokenService;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException
+    {
+        LoginUser loginUser = tokenService.getLoginUser(request);
+        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
+        {
+            tokenService.verifyToken(loginUser);
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
+            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        }
+        chain.doFilter(request, response);
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java
new file mode 100644
index 0000000..e1789f8
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java
@@ -0,0 +1,34 @@
+package com.ruoyi.framework.security.handle;
+
+import java.io.IOException;
+import java.io.Serializable;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 认证失败处理类 返回未授权
+ * 
+ * @author ruoyi
+ */
+@Component
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
+{
+    private static final long serialVersionUID = -8970718410437077606L;
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
+            throws IOException
+    {
+        int code = HttpStatus.UNAUTHORIZED;
+        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java
new file mode 100644
index 0000000..595bd3f
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java
@@ -0,0 +1,53 @@
+package com.ruoyi.framework.security.handle;
+
+import java.io.IOException;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.framework.manager.factory.AsyncFactory;
+import com.ruoyi.framework.web.service.TokenService;
+
+/**
+ * 自定义退出处理类 返回成功
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
+{
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 退出处理
+     * 
+     * @return
+     */
+    @Override
+    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+            throws IOException, ServletException
+    {
+        LoginUser loginUser = tokenService.getLoginUser(request);
+        if (StringUtils.isNotNull(loginUser))
+        {
+            String userName = loginUser.getUsername();
+            // 删除用户缓存记录
+            tokenService.delLoginUser(loginUser.getToken());
+            // 记录用户退出日志
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
+        }
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java
new file mode 100644
index 0000000..63b03da
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java
@@ -0,0 +1,240 @@
+package com.ruoyi.framework.web.domain;
+
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import com.ruoyi.common.utils.Arith;
+import com.ruoyi.common.utils.ip.IpUtils;
+import com.ruoyi.framework.web.domain.server.Cpu;
+import com.ruoyi.framework.web.domain.server.Jvm;
+import com.ruoyi.framework.web.domain.server.Mem;
+import com.ruoyi.framework.web.domain.server.Sys;
+import com.ruoyi.framework.web.domain.server.SysFile;
+import oshi.SystemInfo;
+import oshi.hardware.CentralProcessor;
+import oshi.hardware.CentralProcessor.TickType;
+import oshi.hardware.GlobalMemory;
+import oshi.hardware.HardwareAbstractionLayer;
+import oshi.software.os.FileSystem;
+import oshi.software.os.OSFileStore;
+import oshi.software.os.OperatingSystem;
+import oshi.util.Util;
+
+/**
+ * 服务器相关信息
+ * 
+ * @author ruoyi
+ */
+public class Server
+{
+    private static final int OSHI_WAIT_SECOND = 1000;
+    
+    /**
+     * CPU相关信息
+     */
+    private Cpu cpu = new Cpu();
+
+    /**
+     * 內存相关信息
+     */
+    private Mem mem = new Mem();
+
+    /**
+     * JVM相关信息
+     */
+    private Jvm jvm = new Jvm();
+
+    /**
+     * 服务器相关信息
+     */
+    private Sys sys = new Sys();
+
+    /**
+     * 磁盘相关信息
+     */
+    private List<SysFile> sysFiles = new LinkedList<SysFile>();
+
+    public Cpu getCpu()
+    {
+        return cpu;
+    }
+
+    public void setCpu(Cpu cpu)
+    {
+        this.cpu = cpu;
+    }
+
+    public Mem getMem()
+    {
+        return mem;
+    }
+
+    public void setMem(Mem mem)
+    {
+        this.mem = mem;
+    }
+
+    public Jvm getJvm()
+    {
+        return jvm;
+    }
+
+    public void setJvm(Jvm jvm)
+    {
+        this.jvm = jvm;
+    }
+
+    public Sys getSys()
+    {
+        return sys;
+    }
+
+    public void setSys(Sys sys)
+    {
+        this.sys = sys;
+    }
+
+    public List<SysFile> getSysFiles()
+    {
+        return sysFiles;
+    }
+
+    public void setSysFiles(List<SysFile> sysFiles)
+    {
+        this.sysFiles = sysFiles;
+    }
+
+    public void copyTo() throws Exception
+    {
+        SystemInfo si = new SystemInfo();
+        HardwareAbstractionLayer hal = si.getHardware();
+
+        setCpuInfo(hal.getProcessor());
+
+        setMemInfo(hal.getMemory());
+
+        setSysInfo();
+
+        setJvmInfo();
+
+        setSysFiles(si.getOperatingSystem());
+    }
+
+    /**
+     * 设置CPU信息
+     */
+    private void setCpuInfo(CentralProcessor processor)
+    {
+        // CPU信息
+        long[] prevTicks = processor.getSystemCpuLoadTicks();
+        Util.sleep(OSHI_WAIT_SECOND);
+        long[] ticks = processor.getSystemCpuLoadTicks();
+        long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
+        long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
+        long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
+        long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
+        long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
+        long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
+        long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
+        long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
+        long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
+        cpu.setCpuNum(processor.getLogicalProcessorCount());
+        cpu.setTotal(totalCpu);
+        cpu.setSys(cSys);
+        cpu.setUsed(user);
+        cpu.setWait(iowait);
+        cpu.setFree(idle);
+    }
+
+    /**
+     * 设置内存信息
+     */
+    private void setMemInfo(GlobalMemory memory)
+    {
+        mem.setTotal(memory.getTotal());
+        mem.setUsed(memory.getTotal() - memory.getAvailable());
+        mem.setFree(memory.getAvailable());
+    }
+
+    /**
+     * 设置服务器信息
+     */
+    private void setSysInfo()
+    {
+        Properties props = System.getProperties();
+        sys.setComputerName(IpUtils.getHostName());
+        sys.setComputerIp(IpUtils.getHostIp());
+        sys.setOsName(props.getProperty("os.name"));
+        sys.setOsArch(props.getProperty("os.arch"));
+        sys.setUserDir(props.getProperty("user.dir"));
+    }
+
+    /**
+     * 设置Java虚拟机
+     */
+    private void setJvmInfo() throws UnknownHostException
+    {
+        Properties props = System.getProperties();
+        jvm.setTotal(Runtime.getRuntime().totalMemory());
+        jvm.setMax(Runtime.getRuntime().maxMemory());
+        jvm.setFree(Runtime.getRuntime().freeMemory());
+        jvm.setVersion(props.getProperty("java.version"));
+        jvm.setHome(props.getProperty("java.home"));
+    }
+
+    /**
+     * 设置磁盘信息
+     */
+    private void setSysFiles(OperatingSystem os)
+    {
+        FileSystem fileSystem = os.getFileSystem();
+        List<OSFileStore> fsArray = fileSystem.getFileStores();
+        for (OSFileStore fs : fsArray)
+        {
+            long free = fs.getUsableSpace();
+            long total = fs.getTotalSpace();
+            long used = total - free;
+            SysFile sysFile = new SysFile();
+            sysFile.setDirName(fs.getMount());
+            sysFile.setSysTypeName(fs.getType());
+            sysFile.setTypeName(fs.getName());
+            sysFile.setTotal(convertFileSize(total));
+            sysFile.setFree(convertFileSize(free));
+            sysFile.setUsed(convertFileSize(used));
+            sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100));
+            sysFiles.add(sysFile);
+        }
+    }
+
+    /**
+     * 字节转换
+     * 
+     * @param size 字节大小
+     * @return 转换后值
+     */
+    public String convertFileSize(long size)
+    {
+        long kb = 1024;
+        long mb = kb * 1024;
+        long gb = mb * 1024;
+        if (size >= gb)
+        {
+            return String.format("%.1f GB", (float) size / gb);
+        }
+        else if (size >= mb)
+        {
+            float f = (float) size / mb;
+            return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
+        }
+        else if (size >= kb)
+        {
+            float f = (float) size / kb;
+            return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
+        }
+        else
+        {
+            return String.format("%d B", size);
+        }
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java
new file mode 100644
index 0000000..a13a66c
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java
@@ -0,0 +1,101 @@
+package com.ruoyi.framework.web.domain.server;
+
+import com.ruoyi.common.utils.Arith;
+
+/**
+ * CPU相关信息
+ * 
+ * @author ruoyi
+ */
+public class Cpu
+{
+    /**
+     * 核心数
+     */
+    private int cpuNum;
+
+    /**
+     * CPU总的使用率
+     */
+    private double total;
+
+    /**
+     * CPU系统使用率
+     */
+    private double sys;
+
+    /**
+     * CPU用户使用率
+     */
+    private double used;
+
+    /**
+     * CPU当前等待率
+     */
+    private double wait;
+
+    /**
+     * CPU当前空闲率
+     */
+    private double free;
+
+    public int getCpuNum()
+    {
+        return cpuNum;
+    }
+
+    public void setCpuNum(int cpuNum)
+    {
+        this.cpuNum = cpuNum;
+    }
+
+    public double getTotal()
+    {
+        return Arith.round(Arith.mul(total, 100), 2);
+    }
+
+    public void setTotal(double total)
+    {
+        this.total = total;
+    }
+
+    public double getSys()
+    {
+        return Arith.round(Arith.mul(sys / total, 100), 2);
+    }
+
+    public void setSys(double sys)
+    {
+        this.sys = sys;
+    }
+
+    public double getUsed()
+    {
+        return Arith.round(Arith.mul(used / total, 100), 2);
+    }
+
+    public void setUsed(double used)
+    {
+        this.used = used;
+    }
+
+    public double getWait()
+    {
+        return Arith.round(Arith.mul(wait / total, 100), 2);
+    }
+
+    public void setWait(double wait)
+    {
+        this.wait = wait;
+    }
+
+    public double getFree()
+    {
+        return Arith.round(Arith.mul(free / total, 100), 2);
+    }
+
+    public void setFree(double free)
+    {
+        this.free = free;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java
new file mode 100644
index 0000000..1fdc6ac
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java
@@ -0,0 +1,130 @@
+package com.ruoyi.framework.web.domain.server;
+
+import java.lang.management.ManagementFactory;
+import com.ruoyi.common.utils.Arith;
+import com.ruoyi.common.utils.DateUtils;
+
+/**
+ * JVM相关信息
+ * 
+ * @author ruoyi
+ */
+public class Jvm
+{
+    /**
+     * 当前JVM占用的内存总数(M)
+     */
+    private double total;
+
+    /**
+     * JVM最大可用内存总数(M)
+     */
+    private double max;
+
+    /**
+     * JVM空闲内存(M)
+     */
+    private double free;
+
+    /**
+     * JDK版本
+     */
+    private String version;
+
+    /**
+     * JDK路径
+     */
+    private String home;
+
+    public double getTotal()
+    {
+        return Arith.div(total, (1024 * 1024), 2);
+    }
+
+    public void setTotal(double total)
+    {
+        this.total = total;
+    }
+
+    public double getMax()
+    {
+        return Arith.div(max, (1024 * 1024), 2);
+    }
+
+    public void setMax(double max)
+    {
+        this.max = max;
+    }
+
+    public double getFree()
+    {
+        return Arith.div(free, (1024 * 1024), 2);
+    }
+
+    public void setFree(double free)
+    {
+        this.free = free;
+    }
+
+    public double getUsed()
+    {
+        return Arith.div(total - free, (1024 * 1024), 2);
+    }
+
+    public double getUsage()
+    {
+        return Arith.mul(Arith.div(total - free, total, 4), 100);
+    }
+
+    /**
+     * 获取JDK名称
+     */
+    public String getName()
+    {
+        return ManagementFactory.getRuntimeMXBean().getVmName();
+    }
+
+    public String getVersion()
+    {
+        return version;
+    }
+
+    public void setVersion(String version)
+    {
+        this.version = version;
+    }
+
+    public String getHome()
+    {
+        return home;
+    }
+
+    public void setHome(String home)
+    {
+        this.home = home;
+    }
+
+    /**
+     * JDK启动时间
+     */
+    public String getStartTime()
+    {
+        return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate());
+    }
+
+    /**
+     * JDK运行时间
+     */
+    public String getRunTime()
+    {
+        return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate());
+    }
+
+    /**
+     * 运行参数
+     */
+    public String getInputArgs()
+    {
+        return ManagementFactory.getRuntimeMXBean().getInputArguments().toString();
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java
new file mode 100644
index 0000000..13eec52
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java
@@ -0,0 +1,61 @@
+package com.ruoyi.framework.web.domain.server;
+
+import com.ruoyi.common.utils.Arith;
+
+/**
+ * 內存相关信息
+ * 
+ * @author ruoyi
+ */
+public class Mem
+{
+    /**
+     * 内存总量
+     */
+    private double total;
+
+    /**
+     * 已用内存
+     */
+    private double used;
+
+    /**
+     * 剩余内存
+     */
+    private double free;
+
+    public double getTotal()
+    {
+        return Arith.div(total, (1024 * 1024 * 1024), 2);
+    }
+
+    public void setTotal(long total)
+    {
+        this.total = total;
+    }
+
+    public double getUsed()
+    {
+        return Arith.div(used, (1024 * 1024 * 1024), 2);
+    }
+
+    public void setUsed(long used)
+    {
+        this.used = used;
+    }
+
+    public double getFree()
+    {
+        return Arith.div(free, (1024 * 1024 * 1024), 2);
+    }
+
+    public void setFree(long free)
+    {
+        this.free = free;
+    }
+
+    public double getUsage()
+    {
+        return Arith.mul(Arith.div(used, total, 4), 100);
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java
new file mode 100644
index 0000000..45d64d9
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java
@@ -0,0 +1,84 @@
+package com.ruoyi.framework.web.domain.server;
+
+/**
+ * 系统相关信息
+ * 
+ * @author ruoyi
+ */
+public class Sys
+{
+    /**
+     * 服务器名称
+     */
+    private String computerName;
+
+    /**
+     * 服务器Ip
+     */
+    private String computerIp;
+
+    /**
+     * 项目路径
+     */
+    private String userDir;
+
+    /**
+     * 操作系统
+     */
+    private String osName;
+
+    /**
+     * 系统架构
+     */
+    private String osArch;
+
+    public String getComputerName()
+    {
+        return computerName;
+    }
+
+    public void setComputerName(String computerName)
+    {
+        this.computerName = computerName;
+    }
+
+    public String getComputerIp()
+    {
+        return computerIp;
+    }
+
+    public void setComputerIp(String computerIp)
+    {
+        this.computerIp = computerIp;
+    }
+
+    public String getUserDir()
+    {
+        return userDir;
+    }
+
+    public void setUserDir(String userDir)
+    {
+        this.userDir = userDir;
+    }
+
+    public String getOsName()
+    {
+        return osName;
+    }
+
+    public void setOsName(String osName)
+    {
+        this.osName = osName;
+    }
+
+    public String getOsArch()
+    {
+        return osArch;
+    }
+
+    public void setOsArch(String osArch)
+    {
+        this.osArch = osArch;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java
new file mode 100644
index 0000000..1320cde
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java
@@ -0,0 +1,114 @@
+package com.ruoyi.framework.web.domain.server;
+
+/**
+ * 系统文件相关信息
+ * 
+ * @author ruoyi
+ */
+public class SysFile
+{
+    /**
+     * 盘符路径
+     */
+    private String dirName;
+
+    /**
+     * 盘符类型
+     */
+    private String sysTypeName;
+
+    /**
+     * 文件类型
+     */
+    private String typeName;
+
+    /**
+     * 总大小
+     */
+    private String total;
+
+    /**
+     * 剩余大小
+     */
+    private String free;
+
+    /**
+     * 已经使用量
+     */
+    private String used;
+
+    /**
+     * 资源的使用率
+     */
+    private double usage;
+
+    public String getDirName()
+    {
+        return dirName;
+    }
+
+    public void setDirName(String dirName)
+    {
+        this.dirName = dirName;
+    }
+
+    public String getSysTypeName()
+    {
+        return sysTypeName;
+    }
+
+    public void setSysTypeName(String sysTypeName)
+    {
+        this.sysTypeName = sysTypeName;
+    }
+
+    public String getTypeName()
+    {
+        return typeName;
+    }
+
+    public void setTypeName(String typeName)
+    {
+        this.typeName = typeName;
+    }
+
+    public String getTotal()
+    {
+        return total;
+    }
+
+    public void setTotal(String total)
+    {
+        this.total = total;
+    }
+
+    public String getFree()
+    {
+        return free;
+    }
+
+    public void setFree(String free)
+    {
+        this.free = free;
+    }
+
+    public String getUsed()
+    {
+        return used;
+    }
+
+    public void setUsed(String used)
+    {
+        this.used = used;
+    }
+
+    public double getUsage()
+    {
+        return usage;
+    }
+
+    public void setUsage(double usage)
+    {
+        this.usage = usage;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..1ca0283
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java
@@ -0,0 +1,145 @@
+package com.ruoyi.framework.web.exception;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingPathVariableException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.exception.DemoModeException;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.html.EscapeUtil;
+
+/**
+ * 全局异常处理器
+ * 
+ * @author ruoyi
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandler
+{
+    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+    /**
+     * 权限校验异常
+     */
+    @ExceptionHandler(AccessDeniedException.class)
+    public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
+        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
+    }
+
+    /**
+     * 请求方式不支持
+     */
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
+            HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 业务异常
+     */
+    @ExceptionHandler(ServiceException.class)
+    public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
+    {
+        log.error(e.getMessage(), e);
+        Integer code = e.getCode();
+        return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 请求路径中缺少必需的路径变量
+     */
+    @ExceptionHandler(MissingPathVariableException.class)
+    public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
+        return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
+    }
+
+    /**
+     * 请求参数类型不匹配
+     */
+    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+    public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        String value = Convert.toStr(e.getValue());
+        if (StringUtils.isNotEmpty(value))
+        {
+            value = EscapeUtil.clean(value);
+        }
+        log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
+        return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value));
+    }
+
+    /**
+     * 拦截未知的运行时异常
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生未知异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 系统异常
+     */
+    @ExceptionHandler(Exception.class)
+    public AjaxResult handleException(Exception e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生系统异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(BindException.class)
+    public AjaxResult handleBindException(BindException e)
+    {
+        log.error(e.getMessage(), e);
+        String message = e.getAllErrors().get(0).getDefaultMessage();
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
+    {
+        log.error(e.getMessage(), e);
+        String message = e.getBindingResult().getFieldError().getDefaultMessage();
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 演示模式异常
+     */
+    @ExceptionHandler(DemoModeException.class)
+    public AjaxResult handleDemoModeException(DemoModeException e)
+    {
+        return AjaxResult.error("演示模式,不允许操作");
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java
new file mode 100644
index 0000000..07d259a
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java
@@ -0,0 +1,159 @@
+package com.ruoyi.framework.web.service;
+
+import java.util.Set;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.security.context.PermissionContextHolder;
+
+/**
+ * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母
+ * 
+ * @author ruoyi
+ */
+@Service("ss")
+public class PermissionService
+{
+    /**
+     * 验证用户是否具备某权限
+     * 
+     * @param permission 权限字符串
+     * @return 用户是否具备某权限
+     */
+    public boolean hasPermi(String permission)
+    {
+        if (StringUtils.isEmpty(permission))
+        {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
+        {
+            return false;
+        }
+        PermissionContextHolder.setContext(permission);
+        return hasPermissions(loginUser.getPermissions(), permission);
+    }
+
+    /**
+     * 验证用户是否不具备某权限,与 hasPermi逻辑相反
+     *
+     * @param permission 权限字符串
+     * @return 用户是否不具备某权限
+     */
+    public boolean lacksPermi(String permission)
+    {
+        return hasPermi(permission) != true;
+    }
+
+    /**
+     * 验证用户是否具有以下任意一个权限
+     *
+     * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表
+     * @return 用户是否具有以下任意一个权限
+     */
+    public boolean hasAnyPermi(String permissions)
+    {
+        if (StringUtils.isEmpty(permissions))
+        {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
+        {
+            return false;
+        }
+        PermissionContextHolder.setContext(permissions);
+        Set<String> authorities = loginUser.getPermissions();
+        for (String permission : permissions.split(Constants.PERMISSION_DELIMETER))
+        {
+            if (permission != null && hasPermissions(authorities, permission))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断用户是否拥有某个角色
+     * 
+     * @param role 角色字符串
+     * @return 用户是否具备某角色
+     */
+    public boolean hasRole(String role)
+    {
+        if (StringUtils.isEmpty(role))
+        {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
+        {
+            return false;
+        }
+        for (SysRole sysRole : loginUser.getUser().getRoles())
+        {
+            String roleKey = sysRole.getRoleKey();
+            if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 验证用户是否不具备某角色,与 isRole逻辑相反。
+     *
+     * @param role 角色名称
+     * @return 用户是否不具备某角色
+     */
+    public boolean lacksRole(String role)
+    {
+        return hasRole(role) != true;
+    }
+
+    /**
+     * 验证用户是否具有以下任意一个角色
+     *
+     * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
+     * @return 用户是否具有以下任意一个角色
+     */
+    public boolean hasAnyRoles(String roles)
+    {
+        if (StringUtils.isEmpty(roles))
+        {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
+        {
+            return false;
+        }
+        for (String role : roles.split(Constants.ROLE_DELIMETER))
+        {
+            if (hasRole(role))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断是否包含权限
+     * 
+     * @param permissions 权限列表
+     * @param permission 权限字符串
+     * @return 用户是否具备某权限
+     */
+    private boolean hasPermissions(Set<String> permissions, String permission)
+    {
+        return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
new file mode 100644
index 0000000..2279f3c
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
@@ -0,0 +1,181 @@
+package com.ruoyi.framework.web.service;
+
+import jakarta.annotation.Resource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.exception.user.BlackListException;
+import com.ruoyi.common.exception.user.CaptchaException;
+import com.ruoyi.common.exception.user.CaptchaExpireException;
+import com.ruoyi.common.exception.user.UserNotExistsException;
+import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.framework.manager.factory.AsyncFactory;
+import com.ruoyi.framework.security.context.AuthenticationContextHolder;
+import com.ruoyi.system.service.ISysConfigService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 登录校验方法
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysLoginService
+{
+    @Autowired
+    private TokenService tokenService;
+
+    @Resource
+    private AuthenticationManager authenticationManager;
+
+    @Autowired
+    private RedisCache redisCache;
+    
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    /**
+     * 登录验证
+     * 
+     * @param username 用户名
+     * @param password 密码
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public String login(String username, String password, String code, String uuid)
+    {
+        // 验证码校验
+        validateCaptcha(username, code, uuid);
+        // 登录前置校验
+        loginPreCheck(username, password);
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
+            AuthenticationContextHolder.setContext(authenticationToken);
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager.authenticate(authenticationToken);
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        finally
+        {
+            AuthenticationContextHolder.clearContext();
+        }
+        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        recordLoginInfo(loginUser.getUserId());
+        // 生成token
+        return tokenService.createToken(loginUser);
+    }
+
+    /**
+     * 校验验证码
+     * 
+     * @param username 用户名
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public void validateCaptcha(String username, String code, String uuid)
+    {
+        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        if (captchaEnabled)
+        {
+            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
+            String captcha = redisCache.getCacheObject(verifyKey);
+            if (captcha == null)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+                throw new CaptchaExpireException();
+            }
+            redisCache.deleteObject(verifyKey);
+            if (!code.equalsIgnoreCase(captcha))
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+                throw new CaptchaException();
+            }
+        }
+    }
+
+    /**
+     * 登录前置校验
+     * @param username 用户名
+     * @param password 用户密码
+     */
+    public void loginPreCheck(String username, String password)
+    {
+        // 用户名或密码为空 错误
+        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
+            throw new UserNotExistsException();
+        }
+        // 密码如果不在指定范围内 错误
+        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
+                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+            throw new UserPasswordNotMatchException();
+        }
+        // 用户名不在指定范围内 错误
+        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
+                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+            throw new UserPasswordNotMatchException();
+        }
+        // IP黑名单校验
+        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
+        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
+            throw new BlackListException();
+        }
+    }
+
+    /**
+     * 记录登录信息
+     *
+     * @param userId 用户ID
+     */
+    public void recordLoginInfo(Long userId)
+    {
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(userId);
+        sysUser.setLoginIp(IpUtils.getIpAddr());
+        sysUser.setLoginDate(DateUtils.getNowDate());
+        userService.updateUserProfile(sysUser);
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java
new file mode 100644
index 0000000..6728c7b
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java
@@ -0,0 +1,86 @@
+package com.ruoyi.framework.web.service;
+
+import java.util.concurrent.TimeUnit;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
+import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.framework.security.context.AuthenticationContextHolder;
+
+/**
+ * 登录密码方法
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysPasswordService
+{
+    @Autowired
+    private RedisCache redisCache;
+
+    @Value(value = "${user.password.maxRetryCount}")
+    private int maxRetryCount;
+
+    @Value(value = "${user.password.lockTime}")
+    private int lockTime;
+
+    /**
+     * 登录账户密码错误次数缓存键名
+     * 
+     * @param username 用户名
+     * @return 缓存键key
+     */
+    private String getCacheKey(String username)
+    {
+        return CacheConstants.PWD_ERR_CNT_KEY + username;
+    }
+
+    public void validate(SysUser user)
+    {
+        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
+        String username = usernamePasswordAuthenticationToken.getName();
+        String password = usernamePasswordAuthenticationToken.getCredentials().toString();
+
+        Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
+
+        if (retryCount == null)
+        {
+            retryCount = 0;
+        }
+
+        if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
+        {
+            throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
+        }
+
+        if (!matches(user, password))
+        {
+            retryCount = retryCount + 1;
+            redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
+            throw new UserPasswordNotMatchException();
+        }
+        else
+        {
+            clearLoginRecordCache(username);
+        }
+    }
+
+    public boolean matches(SysUser user, String rawPassword)
+    {
+        return SecurityUtils.matchesPassword(rawPassword, user.getPassword());
+    }
+
+    public void clearLoginRecordCache(String loginName)
+    {
+        if (redisCache.hasKey(getCacheKey(loginName)))
+        {
+            redisCache.deleteObject(getCacheKey(loginName));
+        }
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java
new file mode 100644
index 0000000..51c4dd9
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java
@@ -0,0 +1,88 @@
+package com.ruoyi.framework.web.service;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysMenuService;
+import com.ruoyi.system.service.ISysRoleService;
+
+/**
+ * 用户权限处理
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysPermissionService
+{
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ISysMenuService menuService;
+
+    /**
+     * 获取角色数据权限
+     * 
+     * @param user 用户信息
+     * @return 角色权限信息
+     */
+    public Set<String> getRolePermission(SysUser user)
+    {
+        Set<String> roles = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin())
+        {
+            roles.add("admin");
+        }
+        else
+        {
+            roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
+        }
+        return roles;
+    }
+
+    /**
+     * 获取菜单数据权限
+     * 
+     * @param user 用户信息
+     * @return 菜单权限信息
+     */
+    public Set<String> getMenuPermission(SysUser user)
+    {
+        Set<String> perms = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin())
+        {
+            perms.add("*:*:*");
+        }
+        else
+        {
+            List<SysRole> roles = user.getRoles();
+            if (!CollectionUtils.isEmpty(roles))
+            {
+                // 多角色设置permissions属性,以便数据权限匹配权限
+                for (SysRole role : roles)
+                {
+                    if (StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL))
+                    {
+                        Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
+                        role.setPermissions(rolePerms);
+                        perms.addAll(rolePerms);
+                    }
+                }
+            }
+            else
+            {
+                perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
+            }
+        }
+        return perms;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java
new file mode 100644
index 0000000..f2afe31
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java
@@ -0,0 +1,115 @@
+package com.ruoyi.framework.web.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.RegisterBody;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.exception.user.CaptchaException;
+import com.ruoyi.common.exception.user.CaptchaExpireException;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.framework.manager.factory.AsyncFactory;
+import com.ruoyi.system.service.ISysConfigService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 注册校验方法
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysRegisterService
+{
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 注册
+     */
+    public String register(RegisterBody registerBody)
+    {
+        String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
+        SysUser sysUser = new SysUser();
+        sysUser.setUserName(username);
+
+        // 验证码开关
+        boolean captchaEnabled = configService.selectCaptchaEnabled();
+        if (captchaEnabled)
+        {
+            validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
+        }
+
+        if (StringUtils.isEmpty(username))
+        {
+            msg = "用户名不能为空";
+        }
+        else if (StringUtils.isEmpty(password))
+        {
+            msg = "用户密码不能为空";
+        }
+        else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
+                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
+        {
+            msg = "账户长度必须在2到20个字符之间";
+        }
+        else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
+                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
+        {
+            msg = "密码长度必须在5到20个字符之间";
+        }
+        else if (!userService.checkUserNameUnique(sysUser))
+        {
+            msg = "保存用户'" + username + "'失败,注册账号已存在";
+        }
+        else
+        {
+            sysUser.setNickName(username);
+            sysUser.setPassword(SecurityUtils.encryptPassword(password));
+            boolean regFlag = userService.registerUser(sysUser);
+            if (!regFlag)
+            {
+                msg = "注册失败,请联系系统管理人员";
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success")));
+            }
+        }
+        return msg;
+    }
+
+    /**
+     * 校验验证码
+     * 
+     * @param username 用户名
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public void validateCaptcha(String username, String code, String uuid)
+    {
+        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
+        String captcha = redisCache.getCacheObject(verifyKey);
+        redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            throw new CaptchaException();
+        }
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
new file mode 100644
index 0000000..a03ebad
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
@@ -0,0 +1,231 @@
+package com.ruoyi.framework.web.service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import jakarta.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.AddressUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+import com.ruoyi.common.utils.uuid.IdUtils;
+import eu.bitwalker.useragentutils.UserAgent;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+/**
+ * token验证处理
+ *
+ * @author ruoyi
+ */
+@Component
+public class TokenService
+{
+    private static final Logger log = LoggerFactory.getLogger(TokenService.class);
+
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    // 令牌秘钥
+    @Value("${token.secret}")
+    private String secret;
+
+    // 令牌有效期(默认30分钟)
+    @Value("${token.expireTime}")
+    private int expireTime;
+
+    protected static final long MILLIS_SECOND = 1000;
+
+    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
+
+    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 获取用户身份信息
+     *
+     * @return 用户信息
+     */
+    public LoginUser getLoginUser(HttpServletRequest request)
+    {
+        // 获取请求携带的令牌
+        String token = getToken(request);
+        if (StringUtils.isNotEmpty(token))
+        {
+            try
+            {
+                Claims claims = parseToken(token);
+                // 解析对应的权限以及用户信息
+                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
+                String userKey = getTokenKey(uuid);
+                LoginUser user = redisCache.getCacheObject(userKey);
+                return user;
+            }
+            catch (Exception e)
+            {
+                log.error("获取用户信息异常'{}'", e.getMessage());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 设置用户身份信息
+     */
+    public void setLoginUser(LoginUser loginUser)
+    {
+        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
+        {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 删除用户身份信息
+     */
+    public void delLoginUser(String token)
+    {
+        if (StringUtils.isNotEmpty(token))
+        {
+            String userKey = getTokenKey(token);
+            redisCache.deleteObject(userKey);
+        }
+    }
+
+    /**
+     * 创建令牌
+     *
+     * @param loginUser 用户信息
+     * @return 令牌
+     */
+    public String createToken(LoginUser loginUser)
+    {
+        String token = IdUtils.fastUUID();
+        loginUser.setToken(token);
+        setUserAgent(loginUser);
+        refreshToken(loginUser);
+
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(Constants.LOGIN_USER_KEY, token);
+        return createToken(claims);
+    }
+
+    /**
+     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
+     *
+     * @param loginUser
+     * @return 令牌
+     */
+    public void verifyToken(LoginUser loginUser)
+    {
+        long expireTime = loginUser.getExpireTime();
+        long currentTime = System.currentTimeMillis();
+        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
+        {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 刷新令牌有效期
+     *
+     * @param loginUser 登录信息
+     */
+    public void refreshToken(LoginUser loginUser)
+    {
+        loginUser.setLoginTime(System.currentTimeMillis());
+        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
+        // 根据uuid将loginUser缓存
+        String userKey = getTokenKey(loginUser.getToken());
+        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
+    }
+
+    /**
+     * 设置用户代理信息
+     *
+     * @param loginUser 登录信息
+     */
+    public void setUserAgent(LoginUser loginUser)
+    {
+        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
+        String ip = IpUtils.getIpAddr();
+        loginUser.setIpaddr(ip);
+        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+        loginUser.setBrowser(userAgent.getBrowser().getName());
+        loginUser.setOs(userAgent.getOperatingSystem().getName());
+    }
+
+    /**
+     * 从数据声明生成令牌
+     *
+     * @param claims 数据声明
+     * @return 令牌
+     */
+    private String createToken(Map<String, Object> claims)
+    {
+        String token = Jwts.builder()
+                .setClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, secret).compact();
+        return token;
+    }
+
+    /**
+     * 从令牌中获取数据声明
+     *
+     * @param token 令牌
+     * @return 数据声明
+     */
+    private Claims parseToken(String token)
+    {
+        return Jwts.parser()
+                .setSigningKey(secret)
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    /**
+     * 从令牌中获取用户名
+     *
+     * @param token 令牌
+     * @return 用户名
+     */
+    public String getUsernameFromToken(String token)
+    {
+        Claims claims = parseToken(token);
+        return claims.getSubject();
+    }
+
+    /**
+     * 获取请求token
+     *
+     * @param request
+     * @return token
+     */
+    private String getToken(HttpServletRequest request)
+    {
+        String token = request.getHeader(header);
+        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
+        {
+            token = token.replace(Constants.TOKEN_PREFIX, "");
+        }
+        return token;
+    }
+
+    private String getTokenKey(String uuid)
+    {
+        return CacheConstants.LOGIN_TOKEN_KEY + uuid;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..5dcdf90
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java
@@ -0,0 +1,66 @@
+package com.ruoyi.framework.web.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.enums.UserStatus;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 用户验证处理
+ *
+ * @author ruoyi
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService
+{
+    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
+
+    @Autowired
+    private ISysUserService userService;
+    
+    @Autowired
+    private SysPasswordService passwordService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
+    {
+        SysUser user = userService.selectUserByUserName(username);
+        if (StringUtils.isNull(user))
+        {
+            log.info("登录用户:{} 不存在.", username);
+            throw new ServiceException(MessageUtils.message("user.not.exists"));
+        }
+        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
+        {
+            log.info("登录用户:{} 已被删除.", username);
+            throw new ServiceException(MessageUtils.message("user.password.delete"));
+        }
+        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
+        {
+            log.info("登录用户:{} 已被停用.", username);
+            throw new ServiceException(MessageUtils.message("user.blocked"));
+        }
+
+        passwordService.validate(user);
+
+        return createLoginUser(user);
+    }
+
+    public UserDetails createLoginUser(SysUser user)
+    {
+        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
+    }
+}
diff --git a/ruoyi-generator/pom.xml b/ruoyi-generator/pom.xml
new file mode 100644
index 0000000..6059070
--- /dev/null
+++ b/ruoyi-generator/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.8.8</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-generator</artifactId>
+
+    <description>
+        generator代码生成
+    </description>
+
+    <dependencies>
+
+        <!-- velocity代码生成使用模板 -->
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+        </dependency>
+
+        <!-- 通用工具-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common</artifactId>
+        </dependency>
+
+        <!-- 阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-3-starter</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java
new file mode 100644
index 0000000..cc4cd14
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java
@@ -0,0 +1,73 @@
+package com.ruoyi.generator.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取代码生成相关配置
+ * 
+ * @author ruoyi
+ */
+@Component
+@ConfigurationProperties(prefix = "gen")
+@PropertySource(value = { "classpath:generator.yml" })
+public class GenConfig
+{
+    /** 作者 */
+    public static String author;
+
+    /** 生成包路径 */
+    public static String packageName;
+
+    /** 自动去除表前缀,默认是false */
+    public static boolean autoRemovePre;
+
+    /** 表前缀(类名不会包含表前缀) */
+    public static String tablePrefix;
+
+    public static String getAuthor()
+    {
+        return author;
+    }
+
+    @Value("${author}")
+    public void setAuthor(String author)
+    {
+        GenConfig.author = author;
+    }
+
+    public static String getPackageName()
+    {
+        return packageName;
+    }
+
+    @Value("${packageName}")
+    public void setPackageName(String packageName)
+    {
+        GenConfig.packageName = packageName;
+    }
+
+    public static boolean getAutoRemovePre()
+    {
+        return autoRemovePre;
+    }
+
+    @Value("${autoRemovePre}")
+    public void setAutoRemovePre(boolean autoRemovePre)
+    {
+        GenConfig.autoRemovePre = autoRemovePre;
+    }
+
+    public static String getTablePrefix()
+    {
+        return tablePrefix;
+    }
+
+    @Value("${tablePrefix}")
+    public void setTablePrefix(String tablePrefix)
+    {
+        GenConfig.tablePrefix = tablePrefix;
+    }
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java
new file mode 100644
index 0000000..d6752e7
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java
@@ -0,0 +1,258 @@
+package com.ruoyi.generator.controller;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.alibaba.druid.DbType;
+import com.alibaba.druid.sql.SQLUtils;
+import com.alibaba.druid.sql.ast.SQLStatement;
+import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.sql.SqlUtil;
+import com.ruoyi.generator.domain.GenTable;
+import com.ruoyi.generator.domain.GenTableColumn;
+import com.ruoyi.generator.service.IGenTableColumnService;
+import com.ruoyi.generator.service.IGenTableService;
+
+/**
+ * 代码生成 操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/code/gen")
+public class GenController extends BaseController
+{
+    @Autowired
+    private IGenTableService genTableService;
+
+    @Autowired
+    private IGenTableColumnService genTableColumnService;
+
+    /**
+     * 查询代码生成列表
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:list')")
+    @GetMapping("/list")
+    public TableDataInfo genList(GenTable genTable)
+    {
+        startPage();
+        List<GenTable> list = genTableService.selectGenTableList(genTable);
+        return getDataTable(list);
+    }
+
+    /**
+     * 修改代码生成业务
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:query')")
+    @GetMapping(value = "/{tableId}")
+    public AjaxResult getInfo(@PathVariable Long tableId)
+    {
+        GenTable table = genTableService.selectGenTableById(tableId);
+        List<GenTable> tables = genTableService.selectGenTableAll();
+        List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId);
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put("info", table);
+        map.put("rows", list);
+        map.put("tables", tables);
+        return success(map);
+    }
+
+    /**
+     * 查询数据库列表
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:list')")
+    @GetMapping("/db/list")
+    public TableDataInfo dataList(GenTable genTable)
+    {
+        startPage();
+        List<GenTable> list = genTableService.selectDbTableList(genTable);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询数据表字段列表
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:list')")
+    @GetMapping(value = "/column/{tableId}")
+    public TableDataInfo columnList(Long tableId)
+    {
+        TableDataInfo dataInfo = new TableDataInfo();
+        List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId);
+        dataInfo.setRows(list);
+        dataInfo.setTotal(list.size());
+        return dataInfo;
+    }
+
+    /**
+     * 导入表结构(保存)
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:import')")
+    @Log(title = "代码生成", businessType = BusinessType.IMPORT)
+    @PostMapping("/importTable")
+    public AjaxResult importTableSave(String tables)
+    {
+        String[] tableNames = Convert.toStrArray(tables);
+        // 查询表信息
+        List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
+        genTableService.importGenTable(tableList, SecurityUtils.getUsername());
+        return success();
+    }
+
+    /**
+     * 创建表结构(保存)
+     */
+    @PreAuthorize("@ss.hasRole('admin')")
+    @Log(title = "创建表", businessType = BusinessType.OTHER)
+    @PostMapping("/createTable")
+    public AjaxResult createTableSave(String sql)
+    {
+        try
+        {
+            SqlUtil.filterKeyword(sql);
+            List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql);
+            List<String> tableNames = new ArrayList<>();
+            for (SQLStatement sqlStatement : sqlStatements)
+            {
+                if (sqlStatement instanceof MySqlCreateTableStatement)
+                {
+                    MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement;
+                    if (genTableService.createTable(createTableStatement.toString()))
+                    {
+                        String tableName = createTableStatement.getTableName().replaceAll("`", "");
+                        tableNames.add(tableName);
+                    }
+                }
+            }
+            List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()]));
+            String operName = SecurityUtils.getUsername();
+            genTableService.importGenTable(tableList, operName);
+            return AjaxResult.success();
+        }
+        catch (Exception e)
+        {
+            logger.error(e.getMessage(), e);
+            return AjaxResult.error("创建表结构异常");
+        }
+    }
+
+    /**
+     * 修改保存代码生成业务
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:edit')")
+    @Log(title = "代码生成", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult editSave(@Validated @RequestBody GenTable genTable)
+    {
+        genTableService.validateEdit(genTable);
+        genTableService.updateGenTable(genTable);
+        return success();
+    }
+
+    /**
+     * 删除代码生成
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:remove')")
+    @Log(title = "代码生成", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{tableIds}")
+    public AjaxResult remove(@PathVariable Long[] tableIds)
+    {
+        genTableService.deleteGenTableByIds(tableIds);
+        return success();
+    }
+
+    /**
+     * 预览代码
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:preview')")
+    @GetMapping("/preview/{tableId}")
+    public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException
+    {
+        Map<String, String> dataMap = genTableService.previewCode(tableId);
+        return success(dataMap);
+    }
+
+    /**
+     * 生成代码(下载方式)
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:code')")
+    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
+    @GetMapping("/download/{tableName}")
+    public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException
+    {
+        byte[] data = genTableService.downloadCode(tableName);
+        genCode(response, data);
+    }
+
+    /**
+     * 生成代码(自定义路径)
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:code')")
+    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
+    @GetMapping("/genCode/{tableName}")
+    public AjaxResult genCode(@PathVariable("tableName") String tableName)
+    {
+        genTableService.generatorCode(tableName);
+        return success();
+    }
+
+    /**
+     * 同步数据库
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:edit')")
+    @Log(title = "代码生成", businessType = BusinessType.UPDATE)
+    @GetMapping("/synchDb/{tableName}")
+    public AjaxResult synchDb(@PathVariable("tableName") String tableName)
+    {
+        genTableService.synchDb(tableName);
+        return success();
+    }
+
+    /**
+     * 批量生成代码
+     */
+    @PreAuthorize("@ss.hasPermi('tool:gen:code')")
+    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
+    @GetMapping("/batchGenCode")
+    public void batchGenCode(HttpServletResponse response, String tables) throws IOException
+    {
+        String[] tableNames = Convert.toStrArray(tables);
+        byte[] data = genTableService.downloadCode(tableNames);
+        genCode(response, data);
+    }
+
+    /**
+     * 生成zip文件
+     */
+    private void genCode(HttpServletResponse response, byte[] data) throws IOException
+    {
+        response.reset();
+        response.addHeader("Access-Control-Allow-Origin", "*");
+        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
+        response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\"");
+        response.addHeader("Content-Length", "" + data.length);
+        response.setContentType("application/octet-stream; charset=UTF-8");
+        IOUtils.write(data, response.getOutputStream());
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java
new file mode 100644
index 0000000..6d185fe
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java
@@ -0,0 +1,385 @@
+package com.ruoyi.generator.domain;
+
+import java.util.List;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import org.apache.commons.lang3.ArrayUtils;
+import com.ruoyi.common.constant.GenConstants;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 业务表 gen_table
+ * 
+ * @author ruoyi
+ */
+public class GenTable extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 编号 */
+    private Long tableId;
+
+    /** 表名称 */
+    @NotBlank(message = "表名称不能为空")
+    private String tableName;
+
+    /** 表描述 */
+    @NotBlank(message = "表描述不能为空")
+    private String tableComment;
+
+    /** 关联父表的表名 */
+    private String subTableName;
+
+    /** 本表关联父表的外键名 */
+    private String subTableFkName;
+
+    /** 实体类名称(首字母大写) */
+    @NotBlank(message = "实体类名称不能为空")
+    private String className;
+
+    /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */
+    private String tplCategory;
+
+    /** 前端类型(element-ui模版 element-plus模版) */
+    private String tplWebType;
+
+    /** 生成包路径 */
+    @NotBlank(message = "生成包路径不能为空")
+    private String packageName;
+
+    /** 生成模块名 */
+    @NotBlank(message = "生成模块名不能为空")
+    private String moduleName;
+
+    /** 生成业务名 */
+    @NotBlank(message = "生成业务名不能为空")
+    private String businessName;
+
+    /** 生成功能名 */
+    @NotBlank(message = "生成功能名不能为空")
+    private String functionName;
+
+    /** 生成作者 */
+    @NotBlank(message = "作者不能为空")
+    private String functionAuthor;
+
+    /** 生成代码方式(0zip压缩包 1自定义路径) */
+    private String genType;
+
+    /** 生成路径(不填默认项目路径) */
+    private String genPath;
+
+    /** 主键信息 */
+    private GenTableColumn pkColumn;
+
+    /** 子表信息 */
+    private GenTable subTable;
+
+    /** 表列信息 */
+    @Valid
+    private List<GenTableColumn> columns;
+
+    /** 其它生成选项 */
+    private String options;
+
+    /** 树编码字段 */
+    private String treeCode;
+
+    /** 树父编码字段 */
+    private String treeParentCode;
+
+    /** 树名称字段 */
+    private String treeName;
+
+    /** 上级菜单ID字段 */
+    private String parentMenuId;
+
+    /** 上级菜单名称字段 */
+    private String parentMenuName;
+
+    public Long getTableId()
+    {
+        return tableId;
+    }
+
+    public void setTableId(Long tableId)
+    {
+        this.tableId = tableId;
+    }
+
+    public String getTableName()
+    {
+        return tableName;
+    }
+
+    public void setTableName(String tableName)
+    {
+        this.tableName = tableName;
+    }
+
+    public String getTableComment()
+    {
+        return tableComment;
+    }
+
+    public void setTableComment(String tableComment)
+    {
+        this.tableComment = tableComment;
+    }
+
+    public String getSubTableName()
+    {
+        return subTableName;
+    }
+
+    public void setSubTableName(String subTableName)
+    {
+        this.subTableName = subTableName;
+    }
+
+    public String getSubTableFkName()
+    {
+        return subTableFkName;
+    }
+
+    public void setSubTableFkName(String subTableFkName)
+    {
+        this.subTableFkName = subTableFkName;
+    }
+
+    public String getClassName()
+    {
+        return className;
+    }
+
+    public void setClassName(String className)
+    {
+        this.className = className;
+    }
+
+    public String getTplCategory()
+    {
+        return tplCategory;
+    }
+
+    public void setTplCategory(String tplCategory)
+    {
+        this.tplCategory = tplCategory;
+    }
+
+    public String getTplWebType()
+    {
+        return tplWebType;
+    }
+
+    public void setTplWebType(String tplWebType)
+    {
+        this.tplWebType = tplWebType;
+    }
+
+    public String getPackageName()
+    {
+        return packageName;
+    }
+
+    public void setPackageName(String packageName)
+    {
+        this.packageName = packageName;
+    }
+
+    public String getModuleName()
+    {
+        return moduleName;
+    }
+
+    public void setModuleName(String moduleName)
+    {
+        this.moduleName = moduleName;
+    }
+
+    public String getBusinessName()
+    {
+        return businessName;
+    }
+
+    public void setBusinessName(String businessName)
+    {
+        this.businessName = businessName;
+    }
+
+    public String getFunctionName()
+    {
+        return functionName;
+    }
+
+    public void setFunctionName(String functionName)
+    {
+        this.functionName = functionName;
+    }
+
+    public String getFunctionAuthor()
+    {
+        return functionAuthor;
+    }
+
+    public void setFunctionAuthor(String functionAuthor)
+    {
+        this.functionAuthor = functionAuthor;
+    }
+
+    public String getGenType()
+    {
+        return genType;
+    }
+
+    public void setGenType(String genType)
+    {
+        this.genType = genType;
+    }
+
+    public String getGenPath()
+    {
+        return genPath;
+    }
+
+    public void setGenPath(String genPath)
+    {
+        this.genPath = genPath;
+    }
+
+    public GenTableColumn getPkColumn()
+    {
+        return pkColumn;
+    }
+
+    public void setPkColumn(GenTableColumn pkColumn)
+    {
+        this.pkColumn = pkColumn;
+    }
+
+    public GenTable getSubTable()
+    {
+        return subTable;
+    }
+
+    public void setSubTable(GenTable subTable)
+    {
+        this.subTable = subTable;
+    }
+
+    public List<GenTableColumn> getColumns()
+    {
+        return columns;
+    }
+
+    public void setColumns(List<GenTableColumn> columns)
+    {
+        this.columns = columns;
+    }
+
+    public String getOptions()
+    {
+        return options;
+    }
+
+    public void setOptions(String options)
+    {
+        this.options = options;
+    }
+
+    public String getTreeCode()
+    {
+        return treeCode;
+    }
+
+    public void setTreeCode(String treeCode)
+    {
+        this.treeCode = treeCode;
+    }
+
+    public String getTreeParentCode()
+    {
+        return treeParentCode;
+    }
+
+    public void setTreeParentCode(String treeParentCode)
+    {
+        this.treeParentCode = treeParentCode;
+    }
+
+    public String getTreeName()
+    {
+        return treeName;
+    }
+
+    public void setTreeName(String treeName)
+    {
+        this.treeName = treeName;
+    }
+
+    public String getParentMenuId()
+    {
+        return parentMenuId;
+    }
+
+    public void setParentMenuId(String parentMenuId)
+    {
+        this.parentMenuId = parentMenuId;
+    }
+
+    public String getParentMenuName()
+    {
+        return parentMenuName;
+    }
+
+    public void setParentMenuName(String parentMenuName)
+    {
+        this.parentMenuName = parentMenuName;
+    }
+
+    public boolean isSub()
+    {
+        return isSub(this.tplCategory);
+    }
+
+    public static boolean isSub(String tplCategory)
+    {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory);
+    }
+
+    public boolean isTree()
+    {
+        return isTree(this.tplCategory);
+    }
+
+    public static boolean isTree(String tplCategory)
+    {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory);
+    }
+
+    public boolean isCrud()
+    {
+        return isCrud(this.tplCategory);
+    }
+
+    public static boolean isCrud(String tplCategory)
+    {
+        return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory);
+    }
+
+    public boolean isSuperColumn(String javaField)
+    {
+        return isSuperColumn(this.tplCategory, javaField);
+    }
+
+    public static boolean isSuperColumn(String tplCategory, String javaField)
+    {
+        if (isTree(tplCategory))
+        {
+            return StringUtils.equalsAnyIgnoreCase(javaField,
+                    ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY));
+        }
+        return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java
new file mode 100644
index 0000000..3dc6d62
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java
@@ -0,0 +1,373 @@
+package com.ruoyi.generator.domain;
+
+import jakarta.validation.constraints.NotBlank;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 代码生成业务字段表 gen_table_column
+ * 
+ * @author ruoyi
+ */
+public class GenTableColumn extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 编号 */
+    private Long columnId;
+
+    /** 归属表编号 */
+    private Long tableId;
+
+    /** 列名称 */
+    private String columnName;
+
+    /** 列描述 */
+    private String columnComment;
+
+    /** 列类型 */
+    private String columnType;
+
+    /** JAVA类型 */
+    private String javaType;
+
+    /** JAVA字段名 */
+    @NotBlank(message = "Java属性不能为空")
+    private String javaField;
+
+    /** 是否主键(1是) */
+    private String isPk;
+
+    /** 是否自增(1是) */
+    private String isIncrement;
+
+    /** 是否必填(1是) */
+    private String isRequired;
+
+    /** 是否为插入字段(1是) */
+    private String isInsert;
+
+    /** 是否编辑字段(1是) */
+    private String isEdit;
+
+    /** 是否列表字段(1是) */
+    private String isList;
+
+    /** 是否查询字段(1是) */
+    private String isQuery;
+
+    /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */
+    private String queryType;
+
+    /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */
+    private String htmlType;
+
+    /** 字典类型 */
+    private String dictType;
+
+    /** 排序 */
+    private Integer sort;
+
+    public void setColumnId(Long columnId)
+    {
+        this.columnId = columnId;
+    }
+
+    public Long getColumnId()
+    {
+        return columnId;
+    }
+
+    public void setTableId(Long tableId)
+    {
+        this.tableId = tableId;
+    }
+
+    public Long getTableId()
+    {
+        return tableId;
+    }
+
+    public void setColumnName(String columnName)
+    {
+        this.columnName = columnName;
+    }
+
+    public String getColumnName()
+    {
+        return columnName;
+    }
+
+    public void setColumnComment(String columnComment)
+    {
+        this.columnComment = columnComment;
+    }
+
+    public String getColumnComment()
+    {
+        return columnComment;
+    }
+
+    public void setColumnType(String columnType)
+    {
+        this.columnType = columnType;
+    }
+
+    public String getColumnType()
+    {
+        return columnType;
+    }
+
+    public void setJavaType(String javaType)
+    {
+        this.javaType = javaType;
+    }
+
+    public String getJavaType()
+    {
+        return javaType;
+    }
+
+    public void setJavaField(String javaField)
+    {
+        this.javaField = javaField;
+    }
+
+    public String getJavaField()
+    {
+        return javaField;
+    }
+
+    public String getCapJavaField()
+    {
+        return StringUtils.capitalize(javaField);
+    }
+
+    public void setIsPk(String isPk)
+    {
+        this.isPk = isPk;
+    }
+
+    public String getIsPk()
+    {
+        return isPk;
+    }
+
+    public boolean isPk()
+    {
+        return isPk(this.isPk);
+    }
+
+    public boolean isPk(String isPk)
+    {
+        return isPk != null && StringUtils.equals("1", isPk);
+    }
+
+    public String getIsIncrement()
+    {
+        return isIncrement;
+    }
+
+    public void setIsIncrement(String isIncrement)
+    {
+        this.isIncrement = isIncrement;
+    }
+
+    public boolean isIncrement()
+    {
+        return isIncrement(this.isIncrement);
+    }
+
+    public boolean isIncrement(String isIncrement)
+    {
+        return isIncrement != null && StringUtils.equals("1", isIncrement);
+    }
+
+    public void setIsRequired(String isRequired)
+    {
+        this.isRequired = isRequired;
+    }
+
+    public String getIsRequired()
+    {
+        return isRequired;
+    }
+
+    public boolean isRequired()
+    {
+        return isRequired(this.isRequired);
+    }
+
+    public boolean isRequired(String isRequired)
+    {
+        return isRequired != null && StringUtils.equals("1", isRequired);
+    }
+
+    public void setIsInsert(String isInsert)
+    {
+        this.isInsert = isInsert;
+    }
+
+    public String getIsInsert()
+    {
+        return isInsert;
+    }
+
+    public boolean isInsert()
+    {
+        return isInsert(this.isInsert);
+    }
+
+    public boolean isInsert(String isInsert)
+    {
+        return isInsert != null && StringUtils.equals("1", isInsert);
+    }
+
+    public void setIsEdit(String isEdit)
+    {
+        this.isEdit = isEdit;
+    }
+
+    public String getIsEdit()
+    {
+        return isEdit;
+    }
+
+    public boolean isEdit()
+    {
+        return isInsert(this.isEdit);
+    }
+
+    public boolean isEdit(String isEdit)
+    {
+        return isEdit != null && StringUtils.equals("1", isEdit);
+    }
+
+    public void setIsList(String isList)
+    {
+        this.isList = isList;
+    }
+
+    public String getIsList()
+    {
+        return isList;
+    }
+
+    public boolean isList()
+    {
+        return isList(this.isList);
+    }
+
+    public boolean isList(String isList)
+    {
+        return isList != null && StringUtils.equals("1", isList);
+    }
+
+    public void setIsQuery(String isQuery)
+    {
+        this.isQuery = isQuery;
+    }
+
+    public String getIsQuery()
+    {
+        return isQuery;
+    }
+
+    public boolean isQuery()
+    {
+        return isQuery(this.isQuery);
+    }
+
+    public boolean isQuery(String isQuery)
+    {
+        return isQuery != null && StringUtils.equals("1", isQuery);
+    }
+
+    public void setQueryType(String queryType)
+    {
+        this.queryType = queryType;
+    }
+
+    public String getQueryType()
+    {
+        return queryType;
+    }
+
+    public String getHtmlType()
+    {
+        return htmlType;
+    }
+
+    public void setHtmlType(String htmlType)
+    {
+        this.htmlType = htmlType;
+    }
+
+    public void setDictType(String dictType)
+    {
+        this.dictType = dictType;
+    }
+
+    public String getDictType()
+    {
+        return dictType;
+    }
+
+    public void setSort(Integer sort)
+    {
+        this.sort = sort;
+    }
+
+    public Integer getSort()
+    {
+        return sort;
+    }
+
+    public boolean isSuperColumn()
+    {
+        return isSuperColumn(this.javaField);
+    }
+
+    public static boolean isSuperColumn(String javaField)
+    {
+        return StringUtils.equalsAnyIgnoreCase(javaField,
+                // BaseEntity
+                "createBy", "createTime", "updateBy", "updateTime", "remark",
+                // TreeEntity
+                "parentName", "parentId", "orderNum", "ancestors");
+    }
+
+    public boolean isUsableColumn()
+    {
+        return isUsableColumn(javaField);
+    }
+
+    public static boolean isUsableColumn(String javaField)
+    {
+        // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单
+        return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark");
+    }
+
+    public String readConverterExp()
+    {
+        String remarks = StringUtils.substringBetween(this.columnComment, "(", ")");
+        StringBuffer sb = new StringBuffer();
+        if (StringUtils.isNotEmpty(remarks))
+        {
+            for (String value : remarks.split(" "))
+            {
+                if (StringUtils.isNotEmpty(value))
+                {
+                    Object startStr = value.subSequence(0, 1);
+                    String endStr = value.substring(1);
+                    sb.append("").append(startStr).append("=").append(endStr).append(",");
+                }
+            }
+            return sb.deleteCharAt(sb.length() - 1).toString();
+        }
+        else
+        {
+            return this.columnComment;
+        }
+    }
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java
new file mode 100644
index 0000000..951e166
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java
@@ -0,0 +1,60 @@
+package com.ruoyi.generator.mapper;
+
+import java.util.List;
+import com.ruoyi.generator.domain.GenTableColumn;
+
+/**
+ * 业务字段 数据层
+ * 
+ * @author ruoyi
+ */
+public interface GenTableColumnMapper
+{
+    /**
+     * 根据表名称查询列信息
+     * 
+     * @param tableName 表名称
+     * @return 列信息
+     */
+    public List<GenTableColumn> selectDbTableColumnsByName(String tableName);
+
+    /**
+     * 查询业务字段列表
+     * 
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+    public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
+
+    /**
+     * 新增业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    public int insertGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 修改业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    public int updateGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 删除业务字段
+     * 
+     * @param genTableColumns 列数据
+     * @return 结果
+     */
+    public int deleteGenTableColumns(List<GenTableColumn> genTableColumns);
+
+    /**
+     * 批量删除业务字段
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteGenTableColumnByIds(Long[] ids);
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java
new file mode 100644
index 0000000..937656d
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java
@@ -0,0 +1,91 @@
+package com.ruoyi.generator.mapper;
+
+import java.util.List;
+import com.ruoyi.generator.domain.GenTable;
+
+/**
+ * 业务 数据层
+ * 
+ * @author ruoyi
+ */
+public interface GenTableMapper
+{
+    /**
+     * 查询业务列表
+     * 
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    public List<GenTable> selectGenTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     * 
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     * 
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableListByNames(String[] tableNames);
+
+    /**
+     * 查询所有表信息
+     * 
+     * @return 表信息集合
+     */
+    public List<GenTable> selectGenTableAll();
+
+    /**
+     * 查询表ID业务信息
+     * 
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    public GenTable selectGenTableById(Long id);
+
+    /**
+     * 查询表名称业务信息
+     * 
+     * @param tableName 表名称
+     * @return 业务信息
+     */
+    public GenTable selectGenTableByName(String tableName);
+
+    /**
+     * 新增业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    public int insertGenTable(GenTable genTable);
+
+    /**
+     * 修改业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    public int updateGenTable(GenTable genTable);
+
+    /**
+     * 批量删除业务
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteGenTableByIds(Long[] ids);
+
+    /**
+     * 创建表
+     *
+     * @param sql 表结构
+     * @return 结果
+     */
+    public int createTable(String sql);
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java
new file mode 100644
index 0000000..0679689
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java
@@ -0,0 +1,68 @@
+package com.ruoyi.generator.service;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.generator.domain.GenTableColumn;
+import com.ruoyi.generator.mapper.GenTableColumnMapper;
+
+/**
+ * 业务字段 服务层实现
+ * 
+ * @author ruoyi
+ */
+@Service
+public class GenTableColumnServiceImpl implements IGenTableColumnService 
+{
+	@Autowired
+	private GenTableColumnMapper genTableColumnMapper;
+
+	/**
+     * 查询业务字段列表
+     * 
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+	@Override
+	public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId)
+	{
+	    return genTableColumnMapper.selectGenTableColumnListByTableId(tableId);
+	}
+	
+    /**
+     * 新增业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+	@Override
+	public int insertGenTableColumn(GenTableColumn genTableColumn)
+	{
+	    return genTableColumnMapper.insertGenTableColumn(genTableColumn);
+	}
+	
+	/**
+     * 修改业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+	@Override
+	public int updateGenTableColumn(GenTableColumn genTableColumn)
+	{
+	    return genTableColumnMapper.updateGenTableColumn(genTableColumn);
+	}
+
+	/**
+     * 删除业务字段对象
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+	@Override
+	public int deleteGenTableColumnByIds(String ids)
+	{
+		return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids));
+	}
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
new file mode 100644
index 0000000..3fb6383
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
@@ -0,0 +1,531 @@
+package com.ruoyi.generator.service;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.GenConstants;
+import com.ruoyi.common.core.text.CharsetKit;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.generator.domain.GenTable;
+import com.ruoyi.generator.domain.GenTableColumn;
+import com.ruoyi.generator.mapper.GenTableColumnMapper;
+import com.ruoyi.generator.mapper.GenTableMapper;
+import com.ruoyi.generator.util.GenUtils;
+import com.ruoyi.generator.util.VelocityInitializer;
+import com.ruoyi.generator.util.VelocityUtils;
+
+/**
+ * 业务 服务层实现
+ * 
+ * @author ruoyi
+ */
+@Service
+public class GenTableServiceImpl implements IGenTableService
+{
+    private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class);
+
+    @Autowired
+    private GenTableMapper genTableMapper;
+
+    @Autowired
+    private GenTableColumnMapper genTableColumnMapper;
+
+    /**
+     * 查询业务信息
+     * 
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    @Override
+    public GenTable selectGenTableById(Long id)
+    {
+        GenTable genTable = genTableMapper.selectGenTableById(id);
+        setTableFromOptions(genTable);
+        return genTable;
+    }
+
+    /**
+     * 查询业务列表
+     * 
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    @Override
+    public List<GenTable> selectGenTableList(GenTable genTable)
+    {
+        return genTableMapper.selectGenTableList(genTable);
+    }
+
+    /**
+     * 查询据库列表
+     * 
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    @Override
+    public List<GenTable> selectDbTableList(GenTable genTable)
+    {
+        return genTableMapper.selectDbTableList(genTable);
+    }
+
+    /**
+     * 查询据库列表
+     * 
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    @Override
+    public List<GenTable> selectDbTableListByNames(String[] tableNames)
+    {
+        return genTableMapper.selectDbTableListByNames(tableNames);
+    }
+
+    /**
+     * 查询所有表信息
+     * 
+     * @return 表信息集合
+     */
+    @Override
+    public List<GenTable> selectGenTableAll()
+    {
+        return genTableMapper.selectGenTableAll();
+    }
+
+    /**
+     * 修改业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public void updateGenTable(GenTable genTable)
+    {
+        String options = JSON.toJSONString(genTable.getParams());
+        genTable.setOptions(options);
+        int row = genTableMapper.updateGenTable(genTable);
+        if (row > 0)
+        {
+            for (GenTableColumn cenTableColumn : genTable.getColumns())
+            {
+                genTableColumnMapper.updateGenTableColumn(cenTableColumn);
+            }
+        }
+    }
+
+    /**
+     * 删除业务对象
+     * 
+     * @param tableIds 需要删除的数据ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public void deleteGenTableByIds(Long[] tableIds)
+    {
+        genTableMapper.deleteGenTableByIds(tableIds);
+        genTableColumnMapper.deleteGenTableColumnByIds(tableIds);
+    }
+
+    /**
+     * 创建表
+     *
+     * @param sql 创建表语句
+     * @return 结果
+     */
+    @Override
+    public boolean createTable(String sql)
+    {
+        return genTableMapper.createTable(sql) == 0;
+    }
+
+    /**
+     * 导入表结构
+     * 
+     * @param tableList 导入表列表
+     */
+    @Override
+    @Transactional
+    public void importGenTable(List<GenTable> tableList, String operName)
+    {
+        try
+        {
+            for (GenTable table : tableList)
+            {
+                String tableName = table.getTableName();
+                GenUtils.initTable(table, operName);
+                int row = genTableMapper.insertGenTable(table);
+                if (row > 0)
+                {
+                    // 保存列信息
+                    List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
+                    for (GenTableColumn column : genTableColumns)
+                    {
+                        GenUtils.initColumnField(column, table);
+                        genTableColumnMapper.insertGenTableColumn(column);
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 预览代码
+     * 
+     * @param tableId 表编号
+     * @return 预览数据列表
+     */
+    @Override
+    public Map<String, String> previewCode(Long tableId)
+    {
+        Map<String, String> dataMap = new LinkedHashMap<>();
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableById(tableId);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
+        for (String template : templates)
+        {
+            // 渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+            tpl.merge(context, sw);
+            dataMap.put(template, sw.toString());
+        }
+        return dataMap;
+    }
+
+    /**
+     * 生成代码(下载方式)
+     * 
+     * @param tableName 表名称
+     * @return 数据
+     */
+    @Override
+    public byte[] downloadCode(String tableName)
+    {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        generatorCode(tableName, zip);
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 生成代码(自定义路径)
+     * 
+     * @param tableName 表名称
+     */
+    @Override
+    public void generatorCode(String tableName)
+    {
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
+        for (String template : templates)
+        {
+            if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm"))
+            {
+                // 渲染模板
+                StringWriter sw = new StringWriter();
+                Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+                tpl.merge(context, sw);
+                try
+                {
+                    String path = getGenPath(table, template);
+                    FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8);
+                }
+                catch (IOException e)
+                {
+                    throw new ServiceException("渲染模板失败,表名:" + table.getTableName());
+                }
+            }
+        }
+    }
+
+    /**
+     * 同步数据库
+     * 
+     * @param tableName 表名称
+     */
+    @Override
+    @Transactional
+    public void synchDb(String tableName)
+    {
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        List<GenTableColumn> tableColumns = table.getColumns();
+        Map<String, GenTableColumn> tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity()));
+
+        List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
+        if (StringUtils.isEmpty(dbTableColumns))
+        {
+            throw new ServiceException("同步数据失败,原表结构不存在");
+        }
+        List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList());
+
+        dbTableColumns.forEach(column -> {
+            GenUtils.initColumnField(column, table);
+            if (tableColumnMap.containsKey(column.getColumnName()))
+            {
+                GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName());
+                column.setColumnId(prevColumn.getColumnId());
+                if (column.isList())
+                {
+                    // 如果是列表,继续保留查询方式/字典类型选项
+                    column.setDictType(prevColumn.getDictType());
+                    column.setQueryType(prevColumn.getQueryType());
+                }
+                if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
+                        && (column.isInsert() || column.isEdit())
+                        && ((column.isUsableColumn()) || (!column.isSuperColumn())))
+                {
+                    // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项
+                    column.setIsRequired(prevColumn.getIsRequired());
+                    column.setHtmlType(prevColumn.getHtmlType());
+                }
+                genTableColumnMapper.updateGenTableColumn(column);
+            }
+            else
+            {
+                genTableColumnMapper.insertGenTableColumn(column);
+            }
+        });
+
+        List<GenTableColumn> delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
+        if (StringUtils.isNotEmpty(delColumns))
+        {
+            genTableColumnMapper.deleteGenTableColumns(delColumns);
+        }
+    }
+
+    /**
+     * 批量生成代码(下载方式)
+     * 
+     * @param tableNames 表数组
+     * @return 数据
+     */
+    @Override
+    public byte[] downloadCode(String[] tableNames)
+    {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        for (String tableName : tableNames)
+        {
+            generatorCode(tableName, zip);
+        }
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 查询表信息并生成代码
+     */
+    private void generatorCode(String tableName, ZipOutputStream zip)
+    {
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType());
+        for (String template : templates)
+        {
+            // 渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+            tpl.merge(context, sw);
+            try
+            {
+                // 添加到zip
+                zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
+                IOUtils.write(sw.toString(), zip, Constants.UTF8);
+                IOUtils.closeQuietly(sw);
+                zip.flush();
+                zip.closeEntry();
+            }
+            catch (IOException e)
+            {
+                log.error("渲染模板失败,表名:" + table.getTableName(), e);
+            }
+        }
+    }
+
+    /**
+     * 修改保存参数校验
+     * 
+     * @param genTable 业务信息
+     */
+    @Override
+    public void validateEdit(GenTable genTable)
+    {
+        if (GenConstants.TPL_TREE.equals(genTable.getTplCategory()))
+        {
+            String options = JSON.toJSONString(genTable.getParams());
+            JSONObject paramsObj = JSON.parseObject(options);
+            if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE)))
+            {
+                throw new ServiceException("树编码字段不能为空");
+            }
+            else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE)))
+            {
+                throw new ServiceException("树父编码字段不能为空");
+            }
+            else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME)))
+            {
+                throw new ServiceException("树名称字段不能为空");
+            }
+            else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory()))
+            {
+                if (StringUtils.isEmpty(genTable.getSubTableName()))
+                {
+                    throw new ServiceException("关联子表的表名不能为空");
+                }
+                else if (StringUtils.isEmpty(genTable.getSubTableFkName()))
+                {
+                    throw new ServiceException("子表关联的外键名不能为空");
+                }
+            }
+        }
+    }
+
+    /**
+     * 设置主键列信息
+     * 
+     * @param table 业务表信息
+     */
+    public void setPkColumn(GenTable table)
+    {
+        for (GenTableColumn column : table.getColumns())
+        {
+            if (column.isPk())
+            {
+                table.setPkColumn(column);
+                break;
+            }
+        }
+        if (StringUtils.isNull(table.getPkColumn()))
+        {
+            table.setPkColumn(table.getColumns().get(0));
+        }
+        if (GenConstants.TPL_SUB.equals(table.getTplCategory()))
+        {
+            for (GenTableColumn column : table.getSubTable().getColumns())
+            {
+                if (column.isPk())
+                {
+                    table.getSubTable().setPkColumn(column);
+                    break;
+                }
+            }
+            if (StringUtils.isNull(table.getSubTable().getPkColumn()))
+            {
+                table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
+            }
+        }
+    }
+
+    /**
+     * 设置主子表信息
+     * 
+     * @param table 业务表信息
+     */
+    public void setSubTable(GenTable table)
+    {
+        String subTableName = table.getSubTableName();
+        if (StringUtils.isNotEmpty(subTableName))
+        {
+            table.setSubTable(genTableMapper.selectGenTableByName(subTableName));
+        }
+    }
+
+    /**
+     * 设置代码生成其他选项值
+     * 
+     * @param genTable 设置后的生成对象
+     */
+    public void setTableFromOptions(GenTable genTable)
+    {
+        JSONObject paramsObj = JSON.parseObject(genTable.getOptions());
+        if (StringUtils.isNotNull(paramsObj))
+        {
+            String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
+            String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
+            String treeName = paramsObj.getString(GenConstants.TREE_NAME);
+            String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID);
+            String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);
+
+            genTable.setTreeCode(treeCode);
+            genTable.setTreeParentCode(treeParentCode);
+            genTable.setTreeName(treeName);
+            genTable.setParentMenuId(parentMenuId);
+            genTable.setParentMenuName(parentMenuName);
+        }
+    }
+
+    /**
+     * 获取代码生成地址
+     * 
+     * @param table 业务表信息
+     * @param template 模板文件路径
+     * @return 生成地址
+     */
+    public static String getGenPath(GenTable table, String template)
+    {
+        String genPath = table.getGenPath();
+        if (StringUtils.equals(genPath, "/"))
+        {
+            return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
+        }
+        return genPath + File.separator + VelocityUtils.getFileName(template, table);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java
new file mode 100644
index 0000000..3037f70
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java
@@ -0,0 +1,44 @@
+package com.ruoyi.generator.service;
+
+import java.util.List;
+import com.ruoyi.generator.domain.GenTableColumn;
+
+/**
+ * 业务字段 服务层
+ * 
+ * @author ruoyi
+ */
+public interface IGenTableColumnService
+{
+    /**
+     * 查询业务字段列表
+     * 
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+    public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
+
+    /**
+     * 新增业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    public int insertGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 修改业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+    public int updateGenTableColumn(GenTableColumn genTableColumn);
+
+    /**
+     * 删除业务字段信息
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteGenTableColumnByIds(String ids);
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java
new file mode 100644
index 0000000..695426e
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java
@@ -0,0 +1,130 @@
+package com.ruoyi.generator.service;
+
+import java.util.List;
+import java.util.Map;
+import com.ruoyi.generator.domain.GenTable;
+
+/**
+ * 业务 服务层
+ * 
+ * @author ruoyi
+ */
+public interface IGenTableService
+{
+    /**
+     * 查询业务列表
+     * 
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    public List<GenTable> selectGenTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     * 
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableList(GenTable genTable);
+
+    /**
+     * 查询据库列表
+     * 
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    public List<GenTable> selectDbTableListByNames(String[] tableNames);
+
+    /**
+     * 查询所有表信息
+     * 
+     * @return 表信息集合
+     */
+    public List<GenTable> selectGenTableAll();
+
+    /**
+     * 查询业务信息
+     * 
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    public GenTable selectGenTableById(Long id);
+
+    /**
+     * 修改业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    public void updateGenTable(GenTable genTable);
+
+    /**
+     * 删除业务信息
+     * 
+     * @param tableIds 需要删除的表数据ID
+     * @return 结果
+     */
+    public void deleteGenTableByIds(Long[] tableIds);
+
+    /**
+     * 创建表
+     *
+     * @param sql 创建表语句
+     * @return 结果
+     */
+    public boolean createTable(String sql);
+
+    /**
+     * 导入表结构
+     *
+     * @param tableList 导入表列表
+     * @param operName 操作人员
+     */
+    public void importGenTable(List<GenTable> tableList, String operName);
+
+    /**
+     * 预览代码
+     * 
+     * @param tableId 表编号
+     * @return 预览数据列表
+     */
+    public Map<String, String> previewCode(Long tableId);
+
+    /**
+     * 生成代码(下载方式)
+     * 
+     * @param tableName 表名称
+     * @return 数据
+     */
+    public byte[] downloadCode(String tableName);
+
+    /**
+     * 生成代码(自定义路径)
+     * 
+     * @param tableName 表名称
+     * @return 数据
+     */
+    public void generatorCode(String tableName);
+
+    /**
+     * 同步数据库
+     * 
+     * @param tableName 表名称
+     */
+    public void synchDb(String tableName);
+
+    /**
+     * 批量生成代码(下载方式)
+     * 
+     * @param tableNames 表数组
+     * @return 数据
+     */
+    public byte[] downloadCode(String[] tableNames);
+
+    /**
+     * 修改保存参数校验
+     * 
+     * @param genTable 业务信息
+     */
+    public void validateEdit(GenTable genTable);
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java
new file mode 100644
index 0000000..e7ebc20
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java
@@ -0,0 +1,257 @@
+package com.ruoyi.generator.util;
+
+import java.util.Arrays;
+import org.apache.commons.lang3.RegExUtils;
+import com.ruoyi.common.constant.GenConstants;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.generator.config.GenConfig;
+import com.ruoyi.generator.domain.GenTable;
+import com.ruoyi.generator.domain.GenTableColumn;
+
+/**
+ * 代码生成器 工具类
+ * 
+ * @author ruoyi
+ */
+public class GenUtils
+{
+    /**
+     * 初始化表信息
+     */
+    public static void initTable(GenTable genTable, String operName)
+    {
+        genTable.setClassName(convertClassName(genTable.getTableName()));
+        genTable.setPackageName(GenConfig.getPackageName());
+        genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
+        genTable.setBusinessName(getBusinessName(genTable.getTableName()));
+        genTable.setFunctionName(replaceText(genTable.getTableComment()));
+        genTable.setFunctionAuthor(GenConfig.getAuthor());
+        genTable.setCreateBy(operName);
+    }
+
+    /**
+     * 初始化列属性字段
+     */
+    public static void initColumnField(GenTableColumn column, GenTable table)
+    {
+        String dataType = getDbType(column.getColumnType());
+        String columnName = column.getColumnName();
+        column.setTableId(table.getTableId());
+        column.setCreateBy(table.getCreateBy());
+        // 设置java字段名
+        column.setJavaField(StringUtils.toCamelCase(columnName));
+        // 设置默认类型
+        column.setJavaType(GenConstants.TYPE_STRING);
+        column.setQueryType(GenConstants.QUERY_EQ);
+
+        if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType))
+        {
+            // 字符串长度超过500设置为文本域
+            Integer columnLength = getColumnLength(column.getColumnType());
+            String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
+            column.setHtmlType(htmlType);
+        }
+        else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType))
+        {
+            column.setJavaType(GenConstants.TYPE_DATE);
+            column.setHtmlType(GenConstants.HTML_DATETIME);
+        }
+        else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType))
+        {
+            column.setHtmlType(GenConstants.HTML_INPUT);
+
+            // 如果是浮点型 统一用BigDecimal
+            String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
+            if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0)
+            {
+                column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
+            }
+            // 如果是整形
+            else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10)
+            {
+                column.setJavaType(GenConstants.TYPE_INTEGER);
+            }
+            // 长整形
+            else
+            {
+                column.setJavaType(GenConstants.TYPE_LONG);
+            }
+        }
+
+        // 插入字段(默认所有字段都需要插入)
+        column.setIsInsert(GenConstants.REQUIRE);
+
+        // 编辑字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk())
+        {
+            column.setIsEdit(GenConstants.REQUIRE);
+        }
+        // 列表字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk())
+        {
+            column.setIsList(GenConstants.REQUIRE);
+        }
+        // 查询字段
+        if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk())
+        {
+            column.setIsQuery(GenConstants.REQUIRE);
+        }
+
+        // 查询字段类型
+        if (StringUtils.endsWithIgnoreCase(columnName, "name"))
+        {
+            column.setQueryType(GenConstants.QUERY_LIKE);
+        }
+        // 状态字段设置单选框
+        if (StringUtils.endsWithIgnoreCase(columnName, "status"))
+        {
+            column.setHtmlType(GenConstants.HTML_RADIO);
+        }
+        // 类型&性别字段设置下拉框
+        else if (StringUtils.endsWithIgnoreCase(columnName, "type")
+                || StringUtils.endsWithIgnoreCase(columnName, "sex"))
+        {
+            column.setHtmlType(GenConstants.HTML_SELECT);
+        }
+        // 图片字段设置图片上传控件
+        else if (StringUtils.endsWithIgnoreCase(columnName, "image"))
+        {
+            column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD);
+        }
+        // 文件字段设置文件上传控件
+        else if (StringUtils.endsWithIgnoreCase(columnName, "file"))
+        {
+            column.setHtmlType(GenConstants.HTML_FILE_UPLOAD);
+        }
+        // 内容字段设置富文本控件
+        else if (StringUtils.endsWithIgnoreCase(columnName, "content"))
+        {
+            column.setHtmlType(GenConstants.HTML_EDITOR);
+        }
+    }
+
+    /**
+     * 校验数组是否包含指定值
+     * 
+     * @param arr 数组
+     * @param targetValue 值
+     * @return 是否包含
+     */
+    public static boolean arraysContains(String[] arr, String targetValue)
+    {
+        return Arrays.asList(arr).contains(targetValue);
+    }
+
+    /**
+     * 获取模块名
+     * 
+     * @param packageName 包名
+     * @return 模块名
+     */
+    public static String getModuleName(String packageName)
+    {
+        int lastIndex = packageName.lastIndexOf(".");
+        int nameLength = packageName.length();
+        return StringUtils.substring(packageName, lastIndex + 1, nameLength);
+    }
+
+    /**
+     * 获取业务名
+     * 
+     * @param tableName 表名
+     * @return 业务名
+     */
+    public static String getBusinessName(String tableName)
+    {
+        int lastIndex = tableName.lastIndexOf("_");
+        int nameLength = tableName.length();
+        return StringUtils.substring(tableName, lastIndex + 1, nameLength);
+    }
+
+    /**
+     * 表名转换成Java类名
+     * 
+     * @param tableName 表名称
+     * @return 类名
+     */
+    public static String convertClassName(String tableName)
+    {
+        boolean autoRemovePre = GenConfig.getAutoRemovePre();
+        String tablePrefix = GenConfig.getTablePrefix();
+        if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix))
+        {
+            String[] searchList = StringUtils.split(tablePrefix, ",");
+            tableName = replaceFirst(tableName, searchList);
+        }
+        return StringUtils.convertToCamelCase(tableName);
+    }
+
+    /**
+     * 批量替换前缀
+     * 
+     * @param replacementm 替换值
+     * @param searchList 替换列表
+     * @return
+     */
+    public static String replaceFirst(String replacementm, String[] searchList)
+    {
+        String text = replacementm;
+        for (String searchString : searchList)
+        {
+            if (replacementm.startsWith(searchString))
+            {
+                text = replacementm.replaceFirst(searchString, "");
+                break;
+            }
+        }
+        return text;
+    }
+
+    /**
+     * 关键字替换
+     * 
+     * @param text 需要被替换的名字
+     * @return 替换后的名字
+     */
+    public static String replaceText(String text)
+    {
+        return RegExUtils.replaceAll(text, "(?:表|若依)", "");
+    }
+
+    /**
+     * 获取数据库类型字段
+     * 
+     * @param columnType 列类型
+     * @return 截取后的列类型
+     */
+    public static String getDbType(String columnType)
+    {
+        if (StringUtils.indexOf(columnType, "(") > 0)
+        {
+            return StringUtils.substringBefore(columnType, "(");
+        }
+        else
+        {
+            return columnType;
+        }
+    }
+
+    /**
+     * 获取字段长度
+     * 
+     * @param columnType 列类型
+     * @return 截取后的列类型
+     */
+    public static Integer getColumnLength(String columnType)
+    {
+        if (StringUtils.indexOf(columnType, "(") > 0)
+        {
+            String length = StringUtils.substringBetween(columnType, "(", ")");
+            return Integer.valueOf(length);
+        }
+        else
+        {
+            return 0;
+        }
+    }
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java
new file mode 100644
index 0000000..9f69403
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java
@@ -0,0 +1,34 @@
+package com.ruoyi.generator.util;
+
+import java.util.Properties;
+import org.apache.velocity.app.Velocity;
+import com.ruoyi.common.constant.Constants;
+
+/**
+ * VelocityEngine工厂
+ * 
+ * @author ruoyi
+ */
+public class VelocityInitializer
+{
+    /**
+     * 初始化vm方法
+     */
+    public static void initVelocity()
+    {
+        Properties p = new Properties();
+        try
+        {
+            // 加载classpath目录下的vm文件
+            p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+            // 定义字符集
+            p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8);
+            // 初始化Velocity引擎,指定配置Properties
+            Velocity.init(p);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java
new file mode 100644
index 0000000..1a14681
--- /dev/null
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java
@@ -0,0 +1,408 @@
+package com.ruoyi.generator.util;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.velocity.VelocityContext;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.constant.GenConstants;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.generator.domain.GenTable;
+import com.ruoyi.generator.domain.GenTableColumn;
+
+/**
+ * 模板处理工具类
+ * 
+ * @author ruoyi
+ */
+public class VelocityUtils
+{
+    /** 项目空间路径 */
+    private static final String PROJECT_PATH = "main/java";
+
+    /** mybatis空间路径 */
+    private static final String MYBATIS_PATH = "main/resources/mapper";
+
+    /** 默认上级菜单,系统工具 */
+    private static final String DEFAULT_PARENT_MENU_ID = "3";
+
+    /**
+     * 设置模板变量信息
+     *
+     * @return 模板列表
+     */
+    public static VelocityContext prepareContext(GenTable genTable)
+    {
+        String moduleName = genTable.getModuleName();
+        String businessName = genTable.getBusinessName();
+        String packageName = genTable.getPackageName();
+        String tplCategory = genTable.getTplCategory();
+        String functionName = genTable.getFunctionName();
+
+        VelocityContext velocityContext = new VelocityContext();
+        velocityContext.put("tplCategory", genTable.getTplCategory());
+        velocityContext.put("tableName", genTable.getTableName());
+        velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
+        velocityContext.put("ClassName", genTable.getClassName());
+        velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
+        velocityContext.put("moduleName", genTable.getModuleName());
+        velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
+        velocityContext.put("businessName", genTable.getBusinessName());
+        velocityContext.put("basePackage", getPackagePrefix(packageName));
+        velocityContext.put("packageName", packageName);
+        velocityContext.put("author", genTable.getFunctionAuthor());
+        velocityContext.put("datetime", DateUtils.getDate());
+        velocityContext.put("pkColumn", genTable.getPkColumn());
+        velocityContext.put("importList", getImportList(genTable));
+        velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
+        velocityContext.put("columns", genTable.getColumns());
+        velocityContext.put("table", genTable);
+        velocityContext.put("dicts", getDicts(genTable));
+        setMenuVelocityContext(velocityContext, genTable);
+        if (GenConstants.TPL_TREE.equals(tplCategory))
+        {
+            setTreeVelocityContext(velocityContext, genTable);
+        }
+        if (GenConstants.TPL_SUB.equals(tplCategory))
+        {
+            setSubVelocityContext(velocityContext, genTable);
+        }
+        return velocityContext;
+    }
+
+    public static void setMenuVelocityContext(VelocityContext context, GenTable genTable)
+    {
+        String options = genTable.getOptions();
+        JSONObject paramsObj = JSON.parseObject(options);
+        String parentMenuId = getParentMenuId(paramsObj);
+        context.put("parentMenuId", parentMenuId);
+    }
+
+    public static void setTreeVelocityContext(VelocityContext context, GenTable genTable)
+    {
+        String options = genTable.getOptions();
+        JSONObject paramsObj = JSON.parseObject(options);
+        String treeCode = getTreecode(paramsObj);
+        String treeParentCode = getTreeParentCode(paramsObj);
+        String treeName = getTreeName(paramsObj);
+
+        context.put("treeCode", treeCode);
+        context.put("treeParentCode", treeParentCode);
+        context.put("treeName", treeName);
+        context.put("expandColumn", getExpandColumn(genTable));
+        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE))
+        {
+            context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE));
+        }
+        if (paramsObj.containsKey(GenConstants.TREE_NAME))
+        {
+            context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME));
+        }
+    }
+
+    public static void setSubVelocityContext(VelocityContext context, GenTable genTable)
+    {
+        GenTable subTable = genTable.getSubTable();
+        String subTableName = genTable.getSubTableName();
+        String subTableFkName = genTable.getSubTableFkName();
+        String subClassName = genTable.getSubTable().getClassName();
+        String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName);
+
+        context.put("subTable", subTable);
+        context.put("subTableName", subTableName);
+        context.put("subTableFkName", subTableFkName);
+        context.put("subTableFkClassName", subTableFkClassName);
+        context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName));
+        context.put("subClassName", subClassName);
+        context.put("subclassName", StringUtils.uncapitalize(subClassName));
+        context.put("subImportList", getImportList(genTable.getSubTable()));
+    }
+
+    /**
+     * 获取模板信息
+     * @param tplCategory 生成的模板
+     * @param tplWebType 前端类型
+     * @return 模板列表
+     */
+    public static List<String> getTemplateList(String tplCategory, String tplWebType)
+    {
+        String useWebType = "vm/vue";
+        if ("element-plus".equals(tplWebType))
+        {
+            useWebType = "vm/vue/v3";
+        }
+        List<String> templates = new ArrayList<String>();
+        templates.add("vm/java/domain.java.vm");
+        templates.add("vm/java/mapper.java.vm");
+        templates.add("vm/java/service.java.vm");
+        templates.add("vm/java/serviceImpl.java.vm");
+        templates.add("vm/java/controller.java.vm");
+        templates.add("vm/xml/mapper.xml.vm");
+        templates.add("vm/sql/sql.vm");
+        templates.add("vm/js/api.js.vm");
+        if (GenConstants.TPL_CRUD.equals(tplCategory))
+        {
+            templates.add(useWebType + "/index.vue.vm");
+        }
+        else if (GenConstants.TPL_TREE.equals(tplCategory))
+        {
+            templates.add(useWebType + "/index-tree.vue.vm");
+        }
+        else if (GenConstants.TPL_SUB.equals(tplCategory))
+        {
+            templates.add(useWebType + "/index.vue.vm");
+            templates.add("vm/java/sub-domain.java.vm");
+        }
+        return templates;
+    }
+
+    /**
+     * 获取文件名
+     */
+    public static String getFileName(String template, GenTable genTable)
+    {
+        // 文件名称
+        String fileName = "";
+        // 包路径
+        String packageName = genTable.getPackageName();
+        // 模块名
+        String moduleName = genTable.getModuleName();
+        // 大写类名
+        String className = genTable.getClassName();
+        // 业务名称
+        String businessName = genTable.getBusinessName();
+
+        String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
+        String mybatisPath = MYBATIS_PATH + "/" + moduleName;
+        String vuePath = "vue";
+
+        if (template.contains("domain.java.vm"))
+        {
+            fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
+        }
+        if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory()))
+        {
+            fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName());
+        }
+        else if (template.contains("mapper.java.vm"))
+        {
+            fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
+        }
+        else if (template.contains("service.java.vm"))
+        {
+            fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
+        }
+        else if (template.contains("serviceImpl.java.vm"))
+        {
+            fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
+        }
+        else if (template.contains("controller.java.vm"))
+        {
+            fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
+        }
+        else if (template.contains("mapper.xml.vm"))
+        {
+            fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
+        }
+        else if (template.contains("sql.vm"))
+        {
+            fileName = businessName + "Menu.sql";
+        }
+        else if (template.contains("api.js.vm"))
+        {
+            fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName);
+        }
+        else if (template.contains("index.vue.vm"))
+        {
+            fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
+        }
+        else if (template.contains("index-tree.vue.vm"))
+        {
+            fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
+        }
+        return fileName;
+    }
+
+    /**
+     * 获取包前缀
+     *
+     * @param packageName 包名称
+     * @return 包前缀名称
+     */
+    public static String getPackagePrefix(String packageName)
+    {
+        int lastIndex = packageName.lastIndexOf(".");
+        return StringUtils.substring(packageName, 0, lastIndex);
+    }
+
+    /**
+     * 根据列类型获取导入包
+     * 
+     * @param genTable 业务表对象
+     * @return 返回需要导入的包列表
+     */
+    public static HashSet<String> getImportList(GenTable genTable)
+    {
+        List<GenTableColumn> columns = genTable.getColumns();
+        GenTable subGenTable = genTable.getSubTable();
+        HashSet<String> importList = new HashSet<String>();
+        if (StringUtils.isNotNull(subGenTable))
+        {
+            importList.add("java.util.List");
+        }
+        for (GenTableColumn column : columns)
+        {
+            if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType()))
+            {
+                importList.add("java.util.Date");
+                importList.add("com.fasterxml.jackson.annotation.JsonFormat");
+            }
+            else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType()))
+            {
+                importList.add("java.math.BigDecimal");
+            }
+        }
+        return importList;
+    }
+
+    /**
+     * 根据列类型获取字典组
+     * 
+     * @param genTable 业务表对象
+     * @return 返回字典组
+     */
+    public static String getDicts(GenTable genTable)
+    {
+        List<GenTableColumn> columns = genTable.getColumns();
+        Set<String> dicts = new HashSet<String>();
+        addDicts(dicts, columns);
+        if (StringUtils.isNotNull(genTable.getSubTable()))
+        {
+            List<GenTableColumn> subColumns = genTable.getSubTable().getColumns();
+            addDicts(dicts, subColumns);
+        }
+        return StringUtils.join(dicts, ", ");
+    }
+
+    /**
+     * 添加字典列表
+     * 
+     * @param dicts 字典列表
+     * @param columns 列集合
+     */
+    public static void addDicts(Set<String> dicts, List<GenTableColumn> columns)
+    {
+        for (GenTableColumn column : columns)
+        {
+            if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
+                    column.getHtmlType(),
+                    new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX }))
+            {
+                dicts.add("'" + column.getDictType() + "'");
+            }
+        }
+    }
+
+    /**
+     * 获取权限前缀
+     *
+     * @param moduleName 模块名称
+     * @param businessName 业务名称
+     * @return 返回权限前缀
+     */
+    public static String getPermissionPrefix(String moduleName, String businessName)
+    {
+        return StringUtils.format("{}:{}", moduleName, businessName);
+    }
+
+    /**
+     * 获取上级菜单ID字段
+     *
+     * @param paramsObj 生成其他选项
+     * @return 上级菜单ID字段
+     */
+    public static String getParentMenuId(JSONObject paramsObj)
+    {
+        if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
+                && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID)))
+        {
+            return paramsObj.getString(GenConstants.PARENT_MENU_ID);
+        }
+        return DEFAULT_PARENT_MENU_ID;
+    }
+
+    /**
+     * 获取树编码
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树编码
+     */
+    public static String getTreecode(JSONObject paramsObj)
+    {
+        if (paramsObj.containsKey(GenConstants.TREE_CODE))
+        {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE));
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 获取树父编码
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树父编码
+     */
+    public static String getTreeParentCode(JSONObject paramsObj)
+    {
+        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE))
+        {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE));
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 获取树名称
+     *
+     * @param paramsObj 生成其他选项
+     * @return 树名称
+     */
+    public static String getTreeName(JSONObject paramsObj)
+    {
+        if (paramsObj.containsKey(GenConstants.TREE_NAME))
+        {
+            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME));
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 获取需要在哪一列上面显示展开按钮
+     *
+     * @param genTable 业务表对象
+     * @return 展开按钮列序号
+     */
+    public static int getExpandColumn(GenTable genTable)
+    {
+        String options = genTable.getOptions();
+        JSONObject paramsObj = JSON.parseObject(options);
+        String treeName = paramsObj.getString(GenConstants.TREE_NAME);
+        int num = 0;
+        for (GenTableColumn column : genTable.getColumns())
+        {
+            if (column.isList())
+            {
+                num++;
+                String columnName = column.getColumnName();
+                if (columnName.equals(treeName))
+                {
+                    break;
+                }
+            }
+        }
+        return num;
+    }
+}
diff --git a/ruoyi-generator/src/main/resources/generator.yml b/ruoyi-generator/src/main/resources/generator.yml
new file mode 100644
index 0000000..7eae68e
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/generator.yml
@@ -0,0 +1,10 @@
+# 代码生成
+gen:
+  # 作者
+  author: ruoyi
+  # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
+  packageName: com.ruoyi.system
+  # 自动去除表前缀,默认是false
+  autoRemovePre: false
+  # 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
+  tablePrefix: sys_
\ No newline at end of file
diff --git a/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
new file mode 100644
index 0000000..52857e8
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.generator.mapper.GenTableColumnMapper">
+
+    <resultMap type="GenTableColumn" id="GenTableColumnResult">
+        <id     property="columnId"       column="column_id"      />
+        <result property="tableId"        column="table_id"       />
+        <result property="columnName"     column="column_name"    />
+        <result property="columnComment"  column="column_comment" />
+        <result property="columnType"     column="column_type"    />
+        <result property="javaType"       column="java_type"      />
+        <result property="javaField"      column="java_field"     />
+        <result property="isPk"           column="is_pk"          />
+        <result property="isIncrement"    column="is_increment"   />
+        <result property="isRequired"     column="is_required"    />
+        <result property="isInsert"       column="is_insert"      />
+        <result property="isEdit"         column="is_edit"        />
+        <result property="isList"         column="is_list"        />
+        <result property="isQuery"        column="is_query"       />
+        <result property="queryType"      column="query_type"     />
+        <result property="htmlType"       column="html_type"      />
+        <result property="dictType"       column="dict_type"      />
+        <result property="sort"           column="sort"           />
+        <result property="createBy"       column="create_by"      />
+        <result property="createTime"     column="create_time"    />
+        <result property="updateBy"       column="update_by"      />
+        <result property="updateTime"     column="update_time"    />
+    </resultMap>
+
+	<sql id="selectGenTableColumnVo">
+        select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column
+    </sql>
+
+    <select id="selectGenTableColumnListByTableId" parameterType="Long" resultMap="GenTableColumnResult">
+        <include refid="selectGenTableColumnVo"/>
+        where table_id = #{tableId}
+        order by sort
+    </select>
+
+    <select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
+		select column_name, (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else '0' end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type
+		from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})
+		order by ordinal_position
+	</select>
+
+    <insert id="insertGenTableColumn" parameterType="GenTableColumn" useGeneratedKeys="true" keyProperty="columnId">
+        insert into gen_table_column (
+			<if test="tableId != null and tableId != ''">table_id,</if>
+			<if test="columnName != null and columnName != ''">column_name,</if>
+			<if test="columnComment != null and columnComment != ''">column_comment,</if>
+			<if test="columnType != null and columnType != ''">column_type,</if>
+			<if test="javaType != null and javaType != ''">java_type,</if>
+			<if test="javaField != null  and javaField != ''">java_field,</if>
+			<if test="isPk != null and isPk != ''">is_pk,</if>
+			<if test="isIncrement != null and isIncrement != ''">is_increment,</if>
+			<if test="isRequired != null and isRequired != ''">is_required,</if>
+			<if test="isInsert != null and isInsert != ''">is_insert,</if>
+			<if test="isEdit != null and isEdit != ''">is_edit,</if>
+			<if test="isList != null and isList != ''">is_list,</if>
+			<if test="isQuery != null and isQuery != ''">is_query,</if>
+			<if test="queryType != null and queryType != ''">query_type,</if>
+			<if test="htmlType != null and htmlType != ''">html_type,</if>
+			<if test="dictType != null and dictType != ''">dict_type,</if>
+			<if test="sort != null">sort,</if>
+			<if test="createBy != null and createBy != ''">create_by,</if>
+			create_time
+         )values(
+			<if test="tableId != null and tableId != ''">#{tableId},</if>
+			<if test="columnName != null and columnName != ''">#{columnName},</if>
+			<if test="columnComment != null and columnComment != ''">#{columnComment},</if>
+			<if test="columnType != null and columnType != ''">#{columnType},</if>
+			<if test="javaType != null and javaType != ''">#{javaType},</if>
+			<if test="javaField != null and javaField != ''">#{javaField},</if>
+			<if test="isPk != null and isPk != ''">#{isPk},</if>
+			<if test="isIncrement != null and isIncrement != ''">#{isIncrement},</if>
+			<if test="isRequired != null and isRequired != ''">#{isRequired},</if>
+			<if test="isInsert != null and isInsert != ''">#{isInsert},</if>
+			<if test="isEdit != null and isEdit != ''">#{isEdit},</if>
+			<if test="isList != null and isList != ''">#{isList},</if>
+			<if test="isQuery != null and isQuery != ''">#{isQuery},</if>
+			<if test="queryType != null and queryType != ''">#{queryType},</if>
+			<if test="htmlType != null and htmlType != ''">#{htmlType},</if>
+			<if test="dictType != null and dictType != ''">#{dictType},</if>
+			<if test="sort != null">#{sort},</if>
+			<if test="createBy != null and createBy != ''">#{createBy},</if>
+			sysdate()
+         )
+    </insert>
+
+    <update id="updateGenTableColumn" parameterType="GenTableColumn">
+        update gen_table_column
+        <set>
+            <if test="columnComment != null">column_comment = #{columnComment},</if>
+            <if test="javaType != null">java_type = #{javaType},</if>
+            <if test="javaField != null">java_field = #{javaField},</if>
+            <if test="isInsert != null">is_insert = #{isInsert},</if>
+            <if test="isEdit != null">is_edit = #{isEdit},</if>
+            <if test="isList != null">is_list = #{isList},</if>
+            <if test="isQuery != null">is_query = #{isQuery},</if>
+            <if test="isRequired != null">is_required = #{isRequired},</if>
+            <if test="queryType != null">query_type = #{queryType},</if>
+            <if test="htmlType != null">html_type = #{htmlType},</if>
+            <if test="dictType != null">dict_type = #{dictType},</if>
+            <if test="sort != null">sort = #{sort},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            update_time = sysdate()
+        </set>
+        where column_id = #{columnId}
+    </update>
+
+    <delete id="deleteGenTableColumnByIds" parameterType="Long">
+        delete from gen_table_column where table_id in
+        <foreach collection="array" item="tableId" open="(" separator="," close=")">
+            #{tableId}
+        </foreach>
+    </delete>
+
+    <delete id="deleteGenTableColumns">
+        delete from gen_table_column where column_id in
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.columnId}
+        </foreach>
+    </delete>
+
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
new file mode 100644
index 0000000..d1110f7
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.generator.mapper.GenTableMapper">
+
+	<resultMap type="GenTable" id="GenTableResult">
+	    <id     property="tableId"        column="table_id"          />
+		<result property="tableName"      column="table_name"        />
+		<result property="tableComment"   column="table_comment"     />
+		<result property="subTableName"   column="sub_table_name"    />
+		<result property="subTableFkName" column="sub_table_fk_name" />
+		<result property="className"      column="class_name"        />
+		<result property="tplCategory"    column="tpl_category"      />
+		<result property="tplWebType"     column="tpl_web_type"      />
+		<result property="packageName"    column="package_name"      />
+		<result property="moduleName"     column="module_name"       />
+		<result property="businessName"   column="business_name"     />
+		<result property="functionName"   column="function_name"     />
+		<result property="functionAuthor" column="function_author"   />
+		<result property="genType"        column="gen_type"          />
+		<result property="genPath"        column="gen_path"          />
+		<result property="options"        column="options"           />
+		<result property="createBy"       column="create_by"         />
+		<result property="createTime"     column="create_time"       />
+		<result property="updateBy"       column="update_by"         />
+		<result property="updateTime"     column="update_time"       />
+		<result property="remark"         column="remark"            />
+		<collection  property="columns"   javaType="java.util.List"  resultMap="GenTableColumnResult" />
+	</resultMap>
+	
+	<resultMap type="GenTableColumn" id="GenTableColumnResult">
+        <id     property="columnId"       column="column_id"      />
+        <result property="tableId"        column="table_id"       />
+        <result property="columnName"     column="column_name"    />
+        <result property="columnComment"  column="column_comment" />
+        <result property="columnType"     column="column_type"    />
+        <result property="javaType"       column="java_type"      />
+        <result property="javaField"      column="java_field"     />
+        <result property="isPk"           column="is_pk"          />
+        <result property="isIncrement"    column="is_increment"   />
+        <result property="isRequired"     column="is_required"    />
+        <result property="isInsert"       column="is_insert"      />
+        <result property="isEdit"         column="is_edit"        />
+        <result property="isList"         column="is_list"        />
+        <result property="isQuery"        column="is_query"       />
+        <result property="queryType"      column="query_type"     />
+        <result property="htmlType"       column="html_type"      />
+        <result property="dictType"       column="dict_type"      />
+        <result property="sort"           column="sort"           />
+        <result property="createBy"       column="create_by"      />
+        <result property="createTime"     column="create_time"    />
+        <result property="updateBy"       column="update_by"      />
+        <result property="updateTime"     column="update_time"    />
+    </resultMap>
+	
+	<sql id="selectGenTableVo">
+        select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table
+    </sql>
+    
+    <select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult">
+		<include refid="selectGenTableVo"/>
+		<where>
+			<if test="tableName != null and tableName != ''">
+				AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
+			</if>
+			<if test="tableComment != null and tableComment != ''">
+				AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
+			</if>
+			<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+				AND date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
+			</if>
+			<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+				AND date_format(create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
+			</if>
+		</where>
+	</select>
+
+	<select id="selectDbTableList" parameterType="GenTable" resultMap="GenTableResult">
+		select table_name, table_comment, create_time, update_time from information_schema.tables
+		where table_schema = (select database())
+		AND table_name NOT LIKE 'qrtz\_%' AND table_name NOT LIKE 'gen\_%'
+		AND table_name NOT IN (select table_name from gen_table)
+		<if test="tableName != null and tableName != ''">
+			AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
+		</if>
+		<if test="tableComment != null and tableComment != ''">
+			AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
+		</if>
+		<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+			AND date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
+		</if>
+		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+			AND date_format(create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
+		</if>
+        order by create_time desc
+	</select>
+	
+	<select id="selectDbTableListByNames" resultMap="GenTableResult">
+		select table_name, table_comment, create_time, update_time from information_schema.tables
+		where table_name NOT LIKE 'qrtz\_%' and table_name NOT LIKE 'gen\_%' and table_schema = (select database())
+		and table_name in
+	    <foreach collection="array" item="name" open="(" separator="," close=")">
+ 			#{name}
+        </foreach> 
+	</select>
+	
+	<select id="selectTableByName" parameterType="String" resultMap="GenTableResult">
+		select table_name, table_comment, create_time, update_time from information_schema.tables
+		where table_comment <![CDATA[ <> ]]> '' and table_schema = (select database())
+		and table_name = #{tableName}
+	</select>
+	
+	<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
+	    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
+			   c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
+		FROM gen_table t
+			 LEFT JOIN gen_table_column c ON t.table_id = c.table_id
+		where t.table_id = #{tableId} order by c.sort
+	</select>
+	
+	<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
+	    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
+			   c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
+		FROM gen_table t
+			 LEFT JOIN gen_table_column c ON t.table_id = c.table_id
+		where t.table_name = #{tableName} order by c.sort
+	</select>
+	
+	<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
+	    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark,
+			   c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
+		FROM gen_table t
+			 LEFT JOIN gen_table_column c ON t.table_id = c.table_id
+		order by c.sort
+	</select>
+	
+	<insert id="insertGenTable" parameterType="GenTable" useGeneratedKeys="true" keyProperty="tableId">
+        insert into gen_table (
+			<if test="tableName != null">table_name,</if>
+			<if test="tableComment != null and tableComment != ''">table_comment,</if>
+			<if test="className != null and className != ''">class_name,</if>
+			<if test="tplCategory != null and tplCategory != ''">tpl_category,</if>
+			<if test="tplWebType != null and tplWebType != ''">tpl_web_type,</if>
+			<if test="packageName != null and packageName != ''">package_name,</if>
+			<if test="moduleName != null and moduleName != ''">module_name,</if>
+			<if test="businessName != null and businessName != ''">business_name,</if>
+			<if test="functionName != null and functionName != ''">function_name,</if>
+			<if test="functionAuthor != null and functionAuthor != ''">function_author,</if>
+			<if test="genType != null and genType != ''">gen_type,</if>
+			<if test="genPath != null and genPath != ''">gen_path,</if>
+			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+			create_time
+         )values(
+			<if test="tableName != null">#{tableName},</if>
+			<if test="tableComment != null and tableComment != ''">#{tableComment},</if>
+			<if test="className != null and className != ''">#{className},</if>
+			<if test="tplCategory != null and tplCategory != ''">#{tplCategory},</if>
+			<if test="tplWebType != null and tplWebType != ''">#{tplWebType},</if>
+			<if test="packageName != null and packageName != ''">#{packageName},</if>
+			<if test="moduleName != null and moduleName != ''">#{moduleName},</if>
+			<if test="businessName != null and businessName != ''">#{businessName},</if>
+			<if test="functionName != null and functionName != ''">#{functionName},</if>
+			<if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if>
+			<if test="genType != null and genType != ''">#{genType},</if>
+			<if test="genPath != null and genPath != ''">#{genPath},</if>
+			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+			sysdate()
+         )
+    </insert>
+    
+    <update id="createTable">
+        ${sql}
+    </update>
+    
+    <update id="updateGenTable" parameterType="GenTable">
+        update gen_table
+        <set>
+            <if test="tableName != null">table_name = #{tableName},</if>
+            <if test="tableComment != null and tableComment != ''">table_comment = #{tableComment},</if>
+            <if test="subTableName != null">sub_table_name = #{subTableName},</if>
+            <if test="subTableFkName != null">sub_table_fk_name = #{subTableFkName},</if>
+            <if test="className != null and className != ''">class_name = #{className},</if>
+            <if test="functionAuthor != null and functionAuthor != ''">function_author = #{functionAuthor},</if>
+            <if test="genType != null and genType != ''">gen_type = #{genType},</if>
+            <if test="genPath != null and genPath != ''">gen_path = #{genPath},</if>
+            <if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if>
+            <if test="tplWebType != null and tplWebType != ''">tpl_web_type = #{tplWebType},</if>
+            <if test="packageName != null and packageName != ''">package_name = #{packageName},</if>
+            <if test="moduleName != null and moduleName != ''">module_name = #{moduleName},</if>
+            <if test="businessName != null and businessName != ''">business_name = #{businessName},</if>
+            <if test="functionName != null and functionName != ''">function_name = #{functionName},</if>
+            <if test="options != null and options != ''">options = #{options},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            update_time = sysdate()
+        </set>
+        where table_id = #{tableId}
+    </update>
+    
+    <delete id="deleteGenTableByIds" parameterType="Long">
+        delete from gen_table where table_id in 
+        <foreach collection="array" item="tableId" open="(" separator="," close=")">
+            #{tableId}
+        </foreach>
+    </delete>
+
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm
new file mode 100644
index 0000000..b8457a3
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm
@@ -0,0 +1,115 @@
+package ${packageName}.controller;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import ${packageName}.domain.${ClassName};
+import ${packageName}.service.I${ClassName}Service;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+#if($table.crud || $table.sub)
+import com.ruoyi.common.core.page.TableDataInfo;
+#elseif($table.tree)
+#end
+
+/**
+ * ${functionName}Controller
+ * 
+ * @author ${author}
+ * @date ${datetime}
+ */
+@RestController
+@RequestMapping("/${moduleName}/${businessName}")
+public class ${ClassName}Controller extends BaseController
+{
+    @Autowired
+    private I${ClassName}Service ${className}Service;
+
+    /**
+     * 查询${functionName}列表
+     */
+    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')")
+    @GetMapping("/list")
+#if($table.crud || $table.sub)
+    public TableDataInfo list(${ClassName} ${className})
+    {
+        startPage();
+        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
+        return getDataTable(list);
+    }
+#elseif($table.tree)
+    public AjaxResult list(${ClassName} ${className})
+    {
+        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
+        return success(list);
+    }
+#end
+
+    /**
+     * 导出${functionName}列表
+     */
+    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')")
+    @Log(title = "${functionName}", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ${ClassName} ${className})
+    {
+        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
+        ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class);
+        util.exportExcel(response, list, "${functionName}数据");
+    }
+
+    /**
+     * 获取${functionName}详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')")
+    @GetMapping(value = "/{${pkColumn.javaField}}")
+    public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField})
+    {
+        return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}));
+    }
+
+    /**
+     * 新增${functionName}
+     */
+    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')")
+    @Log(title = "${functionName}", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ${ClassName} ${className})
+    {
+        return toAjax(${className}Service.insert${ClassName}(${className}));
+    }
+
+    /**
+     * 修改${functionName}
+     */
+    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')")
+    @Log(title = "${functionName}", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody ${ClassName} ${className})
+    {
+        return toAjax(${className}Service.update${ClassName}(${className}));
+    }
+
+    /**
+     * 删除${functionName}
+     */
+    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')")
+    @Log(title = "${functionName}", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{${pkColumn.javaField}s}")
+    public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s)
+    {
+        return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s));
+    }
+}
diff --git a/ruoyi-generator/src/main/resources/vm/java/domain.java.vm b/ruoyi-generator/src/main/resources/vm/java/domain.java.vm
new file mode 100644
index 0000000..bd51c17
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/java/domain.java.vm
@@ -0,0 +1,105 @@
+package ${packageName}.domain;
+
+#foreach ($import in $importList)
+import ${import};
+#end
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+#if($table.crud || $table.sub)
+import com.ruoyi.common.core.domain.BaseEntity;
+#elseif($table.tree)
+import com.ruoyi.common.core.domain.TreeEntity;
+#end
+
+/**
+ * ${functionName}对象 ${tableName}
+ * 
+ * @author ${author}
+ * @date ${datetime}
+ */
+#if($table.crud || $table.sub)
+#set($Entity="BaseEntity")
+#elseif($table.tree)
+#set($Entity="TreeEntity")
+#end
+public class ${ClassName} extends ${Entity}
+{
+    private static final long serialVersionUID = 1L;
+
+#foreach ($column in $columns)
+#if(!$table.isSuperColumn($column.javaField))
+    /** $column.columnComment */
+#if($column.list)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($parentheseIndex != -1)
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+#elseif($column.javaType == 'Date')
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd")
+#else
+    @Excel(name = "${comment}")
+#end
+#end
+    private $column.javaType $column.javaField;
+
+#end
+#end
+#if($table.sub)
+    /** $table.subTable.functionName信息 */
+    private List<${subClassName}> ${subclassName}List;
+
+#end
+#foreach ($column in $columns)
+#if(!$table.isSuperColumn($column.javaField))
+#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]"))
+#set($AttrName=$column.javaField)
+#else
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#end
+    public void set${AttrName}($column.javaType $column.javaField) 
+    {
+        this.$column.javaField = $column.javaField;
+    }
+
+    public $column.javaType get${AttrName}() 
+    {
+        return $column.javaField;
+    }
+#end
+#end
+
+#if($table.sub)
+    public List<${subClassName}> get${subClassName}List()
+    {
+        return ${subclassName}List;
+    }
+
+    public void set${subClassName}List(List<${subClassName}> ${subclassName}List)
+    {
+        this.${subclassName}List = ${subclassName}List;
+    }
+
+#end
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+#foreach ($column in $columns)
+#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]"))
+#set($AttrName=$column.javaField)
+#else
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#end
+            .append("${column.javaField}", get${AttrName}())
+#end
+#if($table.sub)
+            .append("${subclassName}List", get${subClassName}List())
+#end
+            .toString();
+    }
+}
diff --git a/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
new file mode 100644
index 0000000..7e7d7c2
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
@@ -0,0 +1,91 @@
+package ${packageName}.mapper;
+
+import java.util.List;
+import ${packageName}.domain.${ClassName};
+#if($table.sub)
+import ${packageName}.domain.${subClassName};
+#end
+
+/**
+ * ${functionName}Mapper接口
+ * 
+ * @author ${author}
+ * @date ${datetime}
+ */
+public interface ${ClassName}Mapper 
+{
+    /**
+     * 查询${functionName}
+     * 
+     * @param ${pkColumn.javaField} ${functionName}主键
+     * @return ${functionName}
+     */
+    public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
+
+    /**
+     * 查询${functionName}列表
+     * 
+     * @param ${className} ${functionName}
+     * @return ${functionName}集合
+     */
+    public List<${ClassName}> select${ClassName}List(${ClassName} ${className});
+
+    /**
+     * 新增${functionName}
+     * 
+     * @param ${className} ${functionName}
+     * @return 结果
+     */
+    public int insert${ClassName}(${ClassName} ${className});
+
+    /**
+     * 修改${functionName}
+     * 
+     * @param ${className} ${functionName}
+     * @return 结果
+     */
+    public int update${ClassName}(${ClassName} ${className});
+
+    /**
+     * 删除${functionName}
+     * 
+     * @param ${pkColumn.javaField} ${functionName}主键
+     * @return 结果
+     */
+    public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
+
+    /**
+     * 批量删除${functionName}
+     * 
+     * @param ${pkColumn.javaField}s 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
+#if($table.sub)
+
+    /**
+     * 批量删除${subTable.functionName}
+     * 
+     * @param ${pkColumn.javaField}s 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
+    
+    /**
+     * 批量新增${subTable.functionName}
+     * 
+     * @param ${subclassName}List ${subTable.functionName}列表
+     * @return 结果
+     */
+    public int batch${subClassName}(List<${subClassName}> ${subclassName}List);
+    
+
+    /**
+     * 通过${functionName}主键删除${subTable.functionName}信息
+     * 
+     * @param ${pkColumn.javaField} ${functionName}ID
+     * @return 结果
+     */
+    public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField});
+#end
+}
diff --git a/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-generator/src/main/resources/vm/java/service.java.vm
new file mode 100644
index 0000000..264882b
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/java/service.java.vm
@@ -0,0 +1,61 @@
+package ${packageName}.service;
+
+import java.util.List;
+import ${packageName}.domain.${ClassName};
+
+/**
+ * ${functionName}Service接口
+ * 
+ * @author ${author}
+ * @date ${datetime}
+ */
+public interface I${ClassName}Service 
+{
+    /**
+     * 查询${functionName}
+     * 
+     * @param ${pkColumn.javaField} ${functionName}主键
+     * @return ${functionName}
+     */
+    public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
+
+    /**
+     * 查询${functionName}列表
+     * 
+     * @param ${className} ${functionName}
+     * @return ${functionName}集合
+     */
+    public List<${ClassName}> select${ClassName}List(${ClassName} ${className});
+
+    /**
+     * 新增${functionName}
+     * 
+     * @param ${className} ${functionName}
+     * @return 结果
+     */
+    public int insert${ClassName}(${ClassName} ${className});
+
+    /**
+     * 修改${functionName}
+     * 
+     * @param ${className} ${functionName}
+     * @return 结果
+     */
+    public int update${ClassName}(${ClassName} ${className});
+
+    /**
+     * 批量删除${functionName}
+     * 
+     * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合
+     * @return 结果
+     */
+    public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
+
+    /**
+     * 删除${functionName}信息
+     * 
+     * @param ${pkColumn.javaField} ${functionName}主键
+     * @return 结果
+     */
+    public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
+}
diff --git a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
new file mode 100644
index 0000000..14746e1
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
@@ -0,0 +1,169 @@
+package ${packageName}.service.impl;
+
+import java.util.List;
+#foreach ($column in $columns)
+#if($column.javaField == 'createTime' || $column.javaField == 'updateTime')
+import com.ruoyi.common.utils.DateUtils;
+#break
+#end
+#end
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+#if($table.sub)
+import java.util.ArrayList;
+import com.ruoyi.common.utils.StringUtils;
+import org.springframework.transaction.annotation.Transactional;
+import ${packageName}.domain.${subClassName};
+#end
+import ${packageName}.mapper.${ClassName}Mapper;
+import ${packageName}.domain.${ClassName};
+import ${packageName}.service.I${ClassName}Service;
+
+/**
+ * ${functionName}Service业务层处理
+ * 
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Service
+public class ${ClassName}ServiceImpl implements I${ClassName}Service 
+{
+    @Autowired
+    private ${ClassName}Mapper ${className}Mapper;
+
+    /**
+     * 查询${functionName}
+     * 
+     * @param ${pkColumn.javaField} ${functionName}主键
+     * @return ${functionName}
+     */
+    @Override
+    public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField})
+    {
+        return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField});
+    }
+
+    /**
+     * 查询${functionName}列表
+     * 
+     * @param ${className} ${functionName}
+     * @return ${functionName}
+     */
+    @Override
+    public List<${ClassName}> select${ClassName}List(${ClassName} ${className})
+    {
+        return ${className}Mapper.select${ClassName}List(${className});
+    }
+
+    /**
+     * 新增${functionName}
+     * 
+     * @param ${className} ${functionName}
+     * @return 结果
+     */
+#if($table.sub)
+    @Transactional
+#end
+    @Override
+    public int insert${ClassName}(${ClassName} ${className})
+    {
+#foreach ($column in $columns)
+#if($column.javaField == 'createTime')
+        ${className}.setCreateTime(DateUtils.getNowDate());
+#end
+#end
+#if($table.sub)
+        int rows = ${className}Mapper.insert${ClassName}(${className});
+        insert${subClassName}(${className});
+        return rows;
+#else
+        return ${className}Mapper.insert${ClassName}(${className});
+#end
+    }
+
+    /**
+     * 修改${functionName}
+     * 
+     * @param ${className} ${functionName}
+     * @return 结果
+     */
+#if($table.sub)
+    @Transactional
+#end
+    @Override
+    public int update${ClassName}(${ClassName} ${className})
+    {
+#foreach ($column in $columns)
+#if($column.javaField == 'updateTime')
+        ${className}.setUpdateTime(DateUtils.getNowDate());
+#end
+#end
+#if($table.sub)
+        ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}());
+        insert${subClassName}(${className});
+#end
+        return ${className}Mapper.update${ClassName}(${className});
+    }
+
+    /**
+     * 批量删除${functionName}
+     * 
+     * @param ${pkColumn.javaField}s 需要删除的${functionName}主键
+     * @return 结果
+     */
+#if($table.sub)
+    @Transactional
+#end
+    @Override
+    public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s)
+    {
+#if($table.sub)
+        ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s);
+#end
+        return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s);
+    }
+
+    /**
+     * 删除${functionName}信息
+     * 
+     * @param ${pkColumn.javaField} ${functionName}主键
+     * @return 结果
+     */
+#if($table.sub)
+    @Transactional
+#end
+    @Override
+    public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField})
+    {
+#if($table.sub)
+        ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField});
+#end
+        return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField});
+    }
+#if($table.sub)
+
+    /**
+     * 新增${subTable.functionName}信息
+     * 
+     * @param ${className} ${functionName}对象
+     */
+    public void insert${subClassName}(${ClassName} ${className})
+    {
+        List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List();
+        ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}();
+        if (StringUtils.isNotNull(${subclassName}List))
+        {
+            List<${subClassName}> list = new ArrayList<${subClassName}>();
+            for (${subClassName} ${subclassName} : ${subclassName}List)
+            {
+                ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField});
+                list.add(${subclassName});
+            }
+            if (list.size() > 0)
+            {
+                ${className}Mapper.batch${subClassName}(list);
+            }
+        }
+    }
+#end
+}
diff --git a/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm b/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm
new file mode 100644
index 0000000..a3f53eb
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm
@@ -0,0 +1,76 @@
+package ${packageName}.domain;
+
+#foreach ($import in $subImportList)
+import ${import};
+#end
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * ${subTable.functionName}对象 ${subTableName}
+ * 
+ * @author ${author}
+ * @date ${datetime}
+ */
+public class ${subClassName} extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+#foreach ($column in $subTable.columns)
+#if(!$table.isSuperColumn($column.javaField))
+    /** $column.columnComment */
+#if($column.list)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($parentheseIndex != -1)
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+#elseif($column.javaType == 'Date')
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd")
+#else
+    @Excel(name = "${comment}")
+#end
+#end
+    private $column.javaType $column.javaField;
+
+#end
+#end
+#foreach ($column in $subTable.columns)
+#if(!$table.isSuperColumn($column.javaField))
+#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]"))
+#set($AttrName=$column.javaField)
+#else
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#end
+    public void set${AttrName}($column.javaType $column.javaField) 
+    {
+        this.$column.javaField = $column.javaField;
+    }
+
+    public $column.javaType get${AttrName}() 
+    {
+        return $column.javaField;
+    }
+#end
+#end
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+#foreach ($column in $subTable.columns)
+#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]"))
+#set($AttrName=$column.javaField)
+#else
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#end
+            .append("${column.javaField}", get${AttrName}())
+#end
+            .toString();
+    }
+}
diff --git a/ruoyi-generator/src/main/resources/vm/js/api.js.vm b/ruoyi-generator/src/main/resources/vm/js/api.js.vm
new file mode 100644
index 0000000..9295524
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/js/api.js.vm
@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 查询${functionName}列表
+export function list${BusinessName}(query) {
+  return request({
+    url: '/${moduleName}/${businessName}/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询${functionName}详细
+export function get${BusinessName}(${pkColumn.javaField}) {
+  return request({
+    url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
+    method: 'get'
+  })
+}
+
+// 新增${functionName}
+export function add${BusinessName}(data) {
+  return request({
+    url: '/${moduleName}/${businessName}',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改${functionName}
+export function update${BusinessName}(data) {
+  return request({
+    url: '/${moduleName}/${businessName}',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除${functionName}
+export function del${BusinessName}(${pkColumn.javaField}) {
+  return request({
+    url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
+    method: 'delete'
+  })
+}
diff --git a/ruoyi-generator/src/main/resources/vm/sql/sql.vm b/ruoyi-generator/src/main/resources/vm/sql/sql.vm
new file mode 100644
index 0000000..0575583
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/sql/sql.vm
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('${functionName}查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('${functionName}新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('${functionName}修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('${functionName}删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('${functionName}导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file
diff --git a/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
new file mode 100644
index 0000000..4819c2a
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
@@ -0,0 +1,505 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-input
+          v-model="queryParams.${column.javaField}"
+          placeholder="请输入${comment}"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option
+            v-for="dict in dict.type.${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-date-picker clearable
+          v-model="queryParams.${column.javaField}"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择${comment}">
+        </el-date-picker>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      <el-form-item label="${comment}">
+        <el-date-picker
+          v-model="daterange${AttrName}"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+      </el-form-item>
+#end
+#end
+#end
+      <el-form-item>
+	    <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['${moduleName}:${businessName}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="el-icon-sort"
+          size="mini"
+          @click="toggleExpandAll"
+        >展开/折叠</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="${businessName}List"
+      row-key="${treeCode}"
+      :default-expand-all="isExpandAll"
+      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+    >
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+#elseif($column.list && $column.htmlType == "datetime")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
+        <template slot-scope="scope">
+          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $column.dictType)
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template slot-scope="scope">
+#if($column.htmlType == "checkbox")
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $javaField)
+#if(${foreach.index} == 1)
+      <el-table-column label="${comment}" prop="${javaField}" />
+#else
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-plus"
+            @click="handleAdd(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:add']"
+          >新增</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if($column.insert && !$column.pk)
+#if(($column.usableColumn) || (!$column.superColumn))
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if("" != $treeParentCode && $column.javaField == $treeParentCode)
+        <el-form-item label="${comment}" prop="${treeParentCode}">
+          <treeselect v-model="form.${treeParentCode}" :options="${businessName}Options" :normalizer="normalizer" placeholder="请选择${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :value="parseInt(dict.value)"
+#else
+              :value="dict.value"
+#end
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :label="parseInt(dict.value)"
+#else
+              :label="dict.value"
+#end
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+#end
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+
+export default {
+  name: "${BusinessName}",
+#if(${dicts} != '')
+  dicts: [${dicts}],
+#end
+  components: {
+    Treeselect
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // ${functionName}表格数据
+      ${businessName}List: [],
+      // ${functionName}树选项
+      ${businessName}Options: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 是否展开,默认全部展开
+      isExpandAll: true,
+      // 重新渲染表格状态
+      refreshTable: true,
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      // $comment时间范围
+      daterange${AttrName}: [],
+#end
+#end
+      // 查询参数
+      queryParams: {
+#foreach ($column in $columns)
+#if($column.query)
+        $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+#foreach ($column in $columns)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+        $column.javaField: [
+          { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+        ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询${functionName}列表 */
+    getList() {
+      this.loading = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      this.queryParams.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) {
+        this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0];
+        this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1];
+      }
+#end
+#end
+      list${BusinessName}(this.queryParams).then(response => {
+        this.${businessName}List = this.handleTree(response.data, "${treeCode}", "${treeParentCode}");
+        this.loading = false;
+      });
+    },
+    /** 转换${functionName}数据结构 */
+    normalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children;
+      }
+      return {
+        id: node.${treeCode},
+        label: node.${treeName},
+        children: node.children
+      };
+    },
+	/** 查询${functionName}下拉树结构 */
+    getTreeselect() {
+      list${BusinessName}().then(response => {
+        this.${businessName}Options = [];
+        const data = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] };
+        data.children = this.handleTree(response.data, "${treeCode}", "${treeParentCode}");
+        this.${businessName}Options.push(data);
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+        $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+        $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      this.daterange${AttrName} = [];
+#end
+#end
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd(row) {
+      this.reset();
+      this.getTreeselect();
+      if (row != null && row.${treeCode}) {
+        this.form.${treeParentCode} = row.${treeCode};
+      } else {
+        this.form.${treeParentCode} = 0;
+      }
+      this.open = true;
+      this.title = "添加${functionName}";
+    },
+    /** 展开/折叠操作 */
+    toggleExpandAll() {
+      this.refreshTable = false;
+      this.isExpandAll = !this.isExpandAll;
+      this.$nextTick(() => {
+        this.refreshTable = true;
+      });
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.getTreeselect();
+      if (row != null) {
+        this.form.${treeParentCode} = row.${treeParentCode};
+      }
+      get${BusinessName}(row.${pkColumn.javaField}).then(response => {
+        this.form = response.data;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+        this.form.$column.javaField = this.form.${column.javaField}.split(",");
+#end
+#end
+        this.open = true;
+        this.title = "修改${functionName}";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.#[[$]]#refs["form"].validate(valid => {
+        if (valid) {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+          this.form.$column.javaField = this.form.${column.javaField}.join(",");
+#end
+#end
+          if (this.form.${pkColumn.javaField} != null) {
+            update${BusinessName}(this.form).then(response => {
+              this.#[[$modal]]#.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            add${BusinessName}(this.form).then(response => {
+              this.#[[$modal]]#.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(function() {
+        return del${BusinessName}(row.${pkColumn.javaField});
+      }).then(() => {
+        this.getList();
+        this.#[[$modal]]#.msgSuccess("删除成功");
+      }).catch(() => {});
+    }
+  }
+};
+</script>
diff --git a/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
new file mode 100644
index 0000000..6296014
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
@@ -0,0 +1,602 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-input
+          v-model="queryParams.${column.javaField}"
+          placeholder="请输入${comment}"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option
+            v-for="dict in dict.type.${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-date-picker clearable
+          v-model="queryParams.${column.javaField}"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="请选择${comment}">
+        </el-date-picker>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      <el-form-item label="${comment}">
+        <el-date-picker
+          v-model="daterange${AttrName}"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+      </el-form-item>
+#end
+#end
+#end
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['${moduleName}:${businessName}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['${moduleName}:${businessName}:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['${moduleName}:${businessName}:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-hasPermi="['${moduleName}:${businessName}:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#elseif($column.list && $column.htmlType == "datetime")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
+        <template slot-scope="scope">
+          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $column.dictType)
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template slot-scope="scope">
+#if($column.htmlType == "checkbox")
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $javaField)
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if($column.insert && !$column.pk)
+#if(($column.usableColumn) || (!$column.superColumn))
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :value="parseInt(dict.value)"
+#else
+              :value="dict.value"
+#end
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :label="parseInt(dict.value)"
+#else
+              :label="dict.value"
+#end
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="请选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+#end
+#if($table.sub)
+        <el-divider content-position="center">${subTable.functionName}信息</el-divider>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd${subClassName}">添加</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" icon="el-icon-delete" size="mini" @click="handleDelete${subClassName}">删除</el-button>
+          </el-col>
+        </el-row>
+        <el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}">
+          <el-table-column type="selection" width="50" align="center" />
+          <el-table-column label="序号" align="center" prop="index" width="50"/>
+#foreach($column in $subTable.columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk || $javaField == ${subTableFkclassName})
+#elseif($column.list && $column.htmlType == "input")
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template slot-scope="scope">
+              <el-input v-model="scope.row.$javaField" placeholder="请输入$comment" />
+            </template>
+          </el-table-column>
+#elseif($column.list && $column.htmlType == "datetime")
+          <el-table-column label="$comment" prop="${javaField}" width="240">
+            <template slot-scope="scope">
+              <el-date-picker clearable v-model="scope.row.$javaField" type="date" value-format="yyyy-MM-dd" placeholder="请选择$comment" />
+            </template>
+          </el-table-column>
+#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType)
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template slot-scope="scope">
+              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
+                <el-option
+                  v-for="dict in dict.type.$column.dictType"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                ></el-option>
+              </el-select>
+            </template>
+          </el-table-column>
+#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType)
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template slot-scope="scope">
+              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
+                <el-option label="请选择字典生成" value="" />
+              </el-select>
+            </template>
+          </el-table-column>
+#end
+#end
+        </el-table>
+#end
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+
+export default {
+  name: "${BusinessName}",
+#if(${dicts} != '')
+  dicts: [${dicts}],
+#end
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+#if($table.sub)
+      // 子表选中数据
+      checked${subClassName}: [],
+#end
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // ${functionName}表格数据
+      ${businessName}List: [],
+#if($table.sub)
+      // ${subTable.functionName}表格数据
+      ${subclassName}List: [],
+#end
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      // $comment时间范围
+      daterange${AttrName}: [],
+#end
+#end
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+#foreach ($column in $columns)
+#if($column.query)
+        $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+#foreach ($column in $columns)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+        $column.javaField: [
+          { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+        ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询${functionName}列表 */
+    getList() {
+      this.loading = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      this.queryParams.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) {
+        this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0];
+        this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1];
+      }
+#end
+#end
+      list${BusinessName}(this.queryParams).then(response => {
+        this.${businessName}List = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+        $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+        $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+      };
+#if($table.sub)
+      this.${subclassName}List = [];
+#end
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      this.daterange${AttrName} = [];
+#end
+#end
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.${pkColumn.javaField})
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加${functionName}";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const ${pkColumn.javaField} = row.${pkColumn.javaField} || this.ids
+      get${BusinessName}(${pkColumn.javaField}).then(response => {
+        this.form = response.data;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+        this.form.$column.javaField = this.form.${column.javaField}.split(",");
+#end
+#end
+#if($table.sub)
+        this.${subclassName}List = response.data.${subclassName}List;
+#end
+        this.open = true;
+        this.title = "修改${functionName}";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.#[[$]]#refs["form"].validate(valid => {
+        if (valid) {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+          this.form.$column.javaField = this.form.${column.javaField}.join(",");
+#end
+#end
+#if($table.sub)
+          this.form.${subclassName}List = this.${subclassName}List;
+#end
+          if (this.form.${pkColumn.javaField} != null) {
+            update${BusinessName}(this.form).then(response => {
+              this.#[[$modal]]#.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            add${BusinessName}(this.form).then(response => {
+              this.#[[$modal]]#.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ${pkColumn.javaField}s = row.${pkColumn.javaField} || this.ids;
+      this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(function() {
+        return del${BusinessName}(${pkColumn.javaField}s);
+      }).then(() => {
+        this.getList();
+        this.#[[$modal]]#.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+#if($table.sub)
+	/** ${subTable.functionName}序号 */
+    row${subClassName}Index({ row, rowIndex }) {
+      row.index = rowIndex + 1;
+    },
+    /** ${subTable.functionName}添加按钮操作 */
+    handleAdd${subClassName}() {
+      let obj = {};
+#foreach($column in $subTable.columns)
+#if($column.pk || $column.javaField == ${subTableFkclassName})
+#elseif($column.list && "" != $javaField)
+      obj.$column.javaField = "";
+#end
+#end
+      this.${subclassName}List.push(obj);
+    },
+    /** ${subTable.functionName}删除按钮操作 */
+    handleDelete${subClassName}() {
+      if (this.checked${subClassName}.length == 0) {
+        this.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
+      } else {
+        const ${subclassName}List = this.${subclassName}List;
+        const checked${subClassName} = this.checked${subClassName};
+        this.${subclassName}List = ${subclassName}List.filter(function(item) {
+          return checked${subClassName}.indexOf(item.index) == -1
+        });
+      }
+    },
+    /** 复选框选中数据 */
+    handle${subClassName}SelectionChange(selection) {
+      this.checked${subClassName} = selection.map(item => item.index)
+    },
+#end
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download('${moduleName}/${businessName}/export', {
+        ...this.queryParams
+      }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
+    }
+  }
+};
+</script>
diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm
new file mode 100644
index 0000000..c54d62b
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm
@@ -0,0 +1,474 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-input
+          v-model="queryParams.${column.javaField}"
+          placeholder="请输入${comment}"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option
+            v-for="dict in ${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-date-picker clearable
+          v-model="queryParams.${column.javaField}"
+          type="date"
+          value-format="YYYY-MM-DD"
+          placeholder="选择${comment}">
+        </el-date-picker>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      <el-form-item label="${comment}" style="width: 308px">
+        <el-date-picker
+          v-model="daterange${AttrName}"
+          value-format="YYYY-MM-DD"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+      </el-form-item>
+#end
+#end
+#end
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="handleAdd"
+          v-hasPermi="['${moduleName}:${businessName}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="Sort"
+          @click="toggleExpandAll"
+        >展开/折叠</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="${businessName}List"
+      row-key="${treeCode}"
+      :default-expand-all="isExpandAll"
+      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+    >
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+#elseif($column.list && $column.htmlType == "datetime")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
+        <template #default="scope">
+          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $column.dictType)
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template #default="scope">
+#if($column.htmlType == "checkbox")
+          <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+          <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $javaField)
+#if(${foreach.index} == 1)
+      <el-table-column label="${comment}" prop="${javaField}" />
+#else
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
+          <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
+          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+      <el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if($column.insert && !$column.pk)
+#if(($column.usableColumn) || (!$column.superColumn))
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if("" != $treeParentCode && $column.javaField == $treeParentCode)
+        <el-form-item label="${comment}" prop="${treeParentCode}">
+          <el-tree-select
+            v-model="form.${treeParentCode}"
+            :data="${businessName}Options"
+            :props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
+            value-key="${treeCode}"
+            placeholder="请选择${comment}"
+            check-strictly
+          />
+        </el-form-item>
+#elseif($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :value="parseInt(dict.value)"
+#else
+              :value="dict.value"
+#end
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :label="parseInt(dict.value)"
+#else
+              :label="dict.value"
+#end
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+#end
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="${BusinessName}">
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+
+const { proxy } = getCurrentInstance();
+#if(${dicts} != '')
+#set($dictsNoSymbol=$dicts.replace("'", ""))
+const { ${dictsNoSymbol} } = proxy.useDict(${dicts});
+#end
+
+const ${businessName}List = ref([]);
+const ${businessName}Options = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const title = ref("");
+const isExpandAll = ref(true);
+const refreshTable = ref(true);
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const daterange${AttrName} = ref([]);
+#end
+#end
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    #foreach ($column in $columns)
+#if($column.query)
+    $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+  },
+  rules: {
+    #foreach ($column in $columns)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+    $column.javaField: [
+      { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+    ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询${functionName}列表 */
+function getList() {
+  loading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+  queryParams.value.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  if (null != daterange${AttrName} && '' != daterange${AttrName}) {
+    queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0];
+    queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1];
+  }
+#end
+#end
+  list${BusinessName}(queryParams.value).then(response => {
+    ${businessName}List.value = proxy.handleTree(response.data, "${treeCode}", "${treeParentCode}");
+    loading.value = false;
+  });
+}
+
+/** 查询${functionName}下拉树结构 */
+function getTreeselect() {
+  list${BusinessName}().then(response => {
+    ${businessName}Options.value = [];
+    const data = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] };
+    data.children = proxy.handleTree(response.data, "${treeCode}", "${treeParentCode}");
+    ${businessName}Options.value.push(data);
+  });
+}
+	
+// 取消按钮
+function cancel() {
+  open.value = false;
+  reset();
+}
+
+// 表单重置
+function reset() {
+  form.value = {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+    $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+    $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+  };
+  proxy.resetForm("${businessName}Ref");
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  getList();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  daterange${AttrName}.value = [];
+#end
+#end
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+
+/** 新增按钮操作 */
+function handleAdd(row) {
+  reset();
+  getTreeselect();
+  if (row != null && row.${treeCode}) {
+    form.value.${treeParentCode} = row.${treeCode};
+  } else {
+    form.value.${treeParentCode} = 0;
+  }
+  open.value = true;
+  title.value = "添加${functionName}";
+}
+
+/** 展开/折叠操作 */
+function toggleExpandAll() {
+  refreshTable.value = false;
+  isExpandAll.value = !isExpandAll.value;
+  nextTick(() => {
+    refreshTable.value = true;
+  });
+}
+
+/** 修改按钮操作 */
+async function handleUpdate(row) {
+  reset();
+  await getTreeselect();
+  if (row != null) {
+    form.value.${treeParentCode} = row.${treeParentCode};
+  }
+  get${BusinessName}(row.${pkColumn.javaField}).then(response => {
+    form.value = response.data;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+    form.value.$column.javaField = form.value.${column.javaField}.split(",");
+#end
+#end
+    open.value = true;
+    title.value = "修改${functionName}";
+  });
+}
+
+/** 提交按钮 */
+function submitForm() {
+  proxy.#[[$]]#refs["${businessName}Ref"].validate(valid => {
+    if (valid) {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+      form.value.$column.javaField = form.value.${column.javaField}.join(",");
+#end
+#end
+      if (form.value.${pkColumn.javaField} != null) {
+        update${BusinessName}(form.value).then(response => {
+          proxy.#[[$modal]]#.msgSuccess("修改成功");
+          open.value = false;
+          getList();
+        });
+      } else {
+        add${BusinessName}(form.value).then(response => {
+          proxy.#[[$modal]]#.msgSuccess("新增成功");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+
+/** 删除按钮操作 */
+function handleDelete(row) {
+  proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(function() {
+    return del${BusinessName}(row.${pkColumn.javaField});
+  }).then(() => {
+    getList();
+    proxy.#[[$modal]]#.msgSuccess("删除成功");
+  }).catch(() => {});
+}
+
+getList();
+</script>
diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm
new file mode 100644
index 0000000..8b25665
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm
@@ -0,0 +1,590 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-input
+          v-model="queryParams.${column.javaField}"
+          placeholder="请输入${comment}"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option
+            v-for="dict in ${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-date-picker clearable
+          v-model="queryParams.${column.javaField}"
+          type="date"
+          value-format="YYYY-MM-DD"
+          placeholder="请选择${comment}">
+        </el-date-picker>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      <el-form-item label="${comment}" style="width: 308px">
+        <el-date-picker
+          v-model="daterange${AttrName}"
+          value-format="YYYY-MM-DD"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+      </el-form-item>
+#end
+#end
+#end
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="handleAdd"
+          v-hasPermi="['${moduleName}:${businessName}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="Edit"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['${moduleName}:${businessName}:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="Delete"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['${moduleName}:${businessName}:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="Download"
+          @click="handleExport"
+          v-hasPermi="['${moduleName}:${businessName}:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#elseif($column.list && $column.htmlType == "datetime")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
+        <template #default="scope">
+          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $column.dictType)
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template #default="scope">
+#if($column.htmlType == "checkbox")
+          <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+          <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $javaField)
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
+          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    
+    <pagination
+      v-show="total>0"
+      :total="total"
+      v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+      <el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if($column.insert && !$column.pk)
+#if(($column.usableColumn) || (!$column.superColumn))
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}" prop="${field}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :value="parseInt(dict.value)"
+#else
+              :value="dict.value"
+#end
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+#if($column.javaType == "Integer" || $column.javaType == "Long")
+              :label="parseInt(dict.value)"
+#else
+              :label="dict.value"
+#end
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="请选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+#end
+#if($table.sub)
+        <el-divider content-position="center">${subTable.functionName}信息</el-divider>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" icon="Plus" @click="handleAdd${subClassName}">添加</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" icon="Delete" @click="handleDelete${subClassName}">删除</el-button>
+          </el-col>
+        </el-row>
+        <el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}">
+          <el-table-column type="selection" width="50" align="center" />
+          <el-table-column label="序号" align="center" prop="index" width="50"/>
+#foreach($column in $subTable.columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk || $javaField == ${subTableFkclassName})
+#elseif($column.list && $column.htmlType == "input")
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template #default="scope">
+              <el-input v-model="scope.row.$javaField" placeholder="请输入$comment" />
+            </template>
+          </el-table-column>
+#elseif($column.list && $column.htmlType == "datetime")
+          <el-table-column label="$comment" prop="${javaField}" width="240">
+            <template #default="scope">
+              <el-date-picker clearable
+                v-model="scope.row.$javaField"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择$comment">
+              </el-date-picker>
+            </template>
+          </el-table-column>
+#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType)
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template #default="scope">
+              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
+                <el-option
+                  v-for="dict in $column.dictType"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                ></el-option>
+              </el-select>
+            </template>
+          </el-table-column>
+#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType)
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template #default="scope">
+              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
+                <el-option label="请选择字典生成" value="" />
+              </el-select>
+            </template>
+          </el-table-column>
+#end
+#end
+        </el-table>
+#end
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="${BusinessName}">
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+
+const { proxy } = getCurrentInstance();
+#if(${dicts} != '')
+#set($dictsNoSymbol=$dicts.replace("'", ""))
+const { ${dictsNoSymbol} } = proxy.useDict(${dicts});
+#end
+
+const ${businessName}List = ref([]);
+#if($table.sub)
+const ${subclassName}List = ref([]);
+#end
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+#if($table.sub)
+const checked${subClassName} = ref([]);
+#end
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const daterange${AttrName} = ref([]);
+#end
+#end
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    #foreach ($column in $columns)
+#if($column.query)
+    $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+  },
+  rules: {
+    #foreach ($column in $columns)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+    $column.javaField: [
+      { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
+    ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询${functionName}列表 */
+function getList() {
+  loading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+  queryParams.value.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  if (null != daterange${AttrName} && '' != daterange${AttrName}) {
+    queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0];
+    queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1];
+  }
+#end
+#end
+  list${BusinessName}(queryParams.value).then(response => {
+    ${businessName}List.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+
+// 取消按钮
+function cancel() {
+  open.value = false;
+  reset();
+}
+
+// 表单重置
+function reset() {
+  form.value = {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+    $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+    $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+  };
+#if($table.sub)
+  ${subclassName}List.value = [];
+#end
+  proxy.resetForm("${businessName}Ref");
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  daterange${AttrName}.value = [];
+#end
+#end
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+
+// 多选框选中数据
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.${pkColumn.javaField});
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "添加${functionName}";
+}
+
+/** 修改按钮操作 */
+function handleUpdate(row) {
+  reset();
+  const _${pkColumn.javaField} = row.${pkColumn.javaField} || ids.value
+  get${BusinessName}(_${pkColumn.javaField}).then(response => {
+    form.value = response.data;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+    form.value.$column.javaField = form.value.${column.javaField}.split(",");
+#end
+#end
+#if($table.sub)
+    ${subclassName}List.value = response.data.${subclassName}List;
+#end
+    open.value = true;
+    title.value = "修改${functionName}";
+  });
+}
+
+/** 提交按钮 */
+function submitForm() {
+  proxy.#[[$]]#refs["${businessName}Ref"].validate(valid => {
+    if (valid) {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+      form.value.$column.javaField = form.value.${column.javaField}.join(",");
+#end
+#end
+#if($table.sub)
+      form.value.${subclassName}List = ${subclassName}List.value;
+#end
+      if (form.value.${pkColumn.javaField} != null) {
+        update${BusinessName}(form.value).then(response => {
+          proxy.#[[$modal]]#.msgSuccess("修改成功");
+          open.value = false;
+          getList();
+        });
+      } else {
+        add${BusinessName}(form.value).then(response => {
+          proxy.#[[$modal]]#.msgSuccess("新增成功");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+
+/** 删除按钮操作 */
+function handleDelete(row) {
+  const _${pkColumn.javaField}s = row.${pkColumn.javaField} || ids.value;
+  proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?').then(function() {
+    return del${BusinessName}(_${pkColumn.javaField}s);
+  }).then(() => {
+    getList();
+    proxy.#[[$modal]]#.msgSuccess("删除成功");
+  }).catch(() => {});
+}
+
+#if($table.sub)
+/** ${subTable.functionName}序号 */
+function row${subClassName}Index({ row, rowIndex }) {
+  row.index = rowIndex + 1;
+}
+
+/** ${subTable.functionName}添加按钮操作 */
+function handleAdd${subClassName}() {
+  let obj = {};
+#foreach($column in $subTable.columns)
+#if($column.pk || $column.javaField == ${subTableFkclassName})
+#elseif($column.list && "" != $javaField)
+  obj.$column.javaField = "";
+#end
+#end
+  ${subclassName}List.value.push(obj);
+}
+
+/** ${subTable.functionName}删除按钮操作 */
+function handleDelete${subClassName}() {
+  if (checked${subClassName}.value.length == 0) {
+    proxy.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
+  } else {
+    const ${subclassName}s = ${subclassName}List.value;
+    const checked${subClassName}s = checked${subClassName}.value;
+    ${subclassName}List.value = ${subclassName}s.filter(function(item) {
+      return checked${subClassName}s.indexOf(item.index) == -1
+    });
+  }
+}
+
+/** 复选框选中数据 */
+function handle${subClassName}SelectionChange(selection) {
+  checked${subClassName}.value = selection.map(item => item.index)
+}
+
+#end
+/** 导出按钮操作 */
+function handleExport() {
+  proxy.download('${moduleName}/${businessName}/export', {
+    ...queryParams.value
+  }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
+}
+
+getList();
+</script>
diff --git a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm
new file mode 100644
index 0000000..456755b
--- /dev/null
+++ b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="${packageName}.mapper.${ClassName}Mapper">
+    
+    <resultMap type="${ClassName}" id="${ClassName}Result">
+#foreach ($column in $columns)
+        <result property="${column.javaField}"    column="${column.columnName}"    />
+#end
+    </resultMap>
+#if($table.sub)
+
+    <resultMap id="${ClassName}${subClassName}Result" type="${ClassName}" extends="${ClassName}Result">
+        <collection property="${subclassName}List" ofType="${subClassName}" column="${pkColumn.columnName}" select="select${subClassName}List" />
+    </resultMap>
+
+    <resultMap type="${subClassName}" id="${subClassName}Result">
+#foreach ($column in $subTable.columns)
+        <result property="${column.javaField}"    column="${column.columnName}"    />
+#end
+    </resultMap>
+#end
+
+    <sql id="select${ClassName}Vo">
+        select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName}
+    </sql>
+
+    <select id="select${ClassName}List" parameterType="${ClassName}" resultMap="${ClassName}Result">
+        <include refid="select${ClassName}Vo"/>
+        <where>  
+#foreach($column in $columns)
+#set($queryType=$column.queryType)
+#set($javaField=$column.javaField)
+#set($javaType=$column.javaType)
+#set($columnName=$column.columnName)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#if($column.query)
+#if($column.queryType == "EQ")
+            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName = #{$javaField}</if>
+#elseif($queryType == "NE")
+            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName != #{$javaField}</if>
+#elseif($queryType == "GT")
+            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &gt; #{$javaField}</if>
+#elseif($queryType == "GTE")
+            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &gt;= #{$javaField}</if>
+#elseif($queryType == "LT")
+            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &lt; #{$javaField}</if>
+#elseif($queryType == "LTE")
+            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &lt;= #{$javaField}</if>
+#elseif($queryType == "LIKE")
+            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName like concat('%', #{$javaField}, '%')</if>
+#elseif($queryType == "BETWEEN")
+            <if test="params.begin$AttrName != null and params.begin$AttrName != '' and params.end$AttrName != null and params.end$AttrName != ''"> and $columnName between #{params.begin$AttrName} and #{params.end$AttrName}</if>
+#end
+#end
+#end
+        </where>
+    </select>
+    
+    <select id="select${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}" resultMap="#if($table.sub)${ClassName}${subClassName}Result#else${ClassName}Result#end">
+#if($table.crud || $table.tree)
+        <include refid="select${ClassName}Vo"/>
+        where ${pkColumn.columnName} = #{${pkColumn.javaField}}
+#elseif($table.sub)
+        select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end
+        from ${tableName}
+        where ${pkColumn.columnName} = #{${pkColumn.javaField}}
+#end
+    </select>
+#if($table.sub)
+
+    <select id="select${subClassName}List" resultMap="${subClassName}Result">
+        select#foreach ($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end
+        from ${subTableName}
+        where ${subTableFkName} = #{${subTableFkName}}
+    </select>
+#end
+
+    <insert id="insert${ClassName}" parameterType="${ClassName}"#if($pkColumn.increment) useGeneratedKeys="true" keyProperty="$pkColumn.javaField"#end>
+        insert into ${tableName}
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+#foreach($column in $columns)
+#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment)
+            <if test="$column.javaField != null#if($column.javaType == 'String' && $column.required) and $column.javaField != ''#end">$column.columnName,</if>
+#end
+#end
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+#foreach($column in $columns)
+#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment)
+            <if test="$column.javaField != null#if($column.javaType == 'String' && $column.required) and $column.javaField != ''#end">#{$column.javaField},</if>
+#end
+#end
+         </trim>
+    </insert>
+
+    <update id="update${ClassName}" parameterType="${ClassName}">
+        update ${tableName}
+        <trim prefix="SET" suffixOverrides=",">
+#foreach($column in $columns)
+#if($column.columnName != $pkColumn.columnName)
+            <if test="$column.javaField != null#if($column.javaType == 'String' && $column.required) and $column.javaField != ''#end">$column.columnName = #{$column.javaField},</if>
+#end
+#end
+        </trim>
+        where ${pkColumn.columnName} = #{${pkColumn.javaField}}
+    </update>
+
+    <delete id="delete${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}">
+        delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}}
+    </delete>
+
+    <delete id="delete${ClassName}By${pkColumn.capJavaField}s" parameterType="String">
+        delete from ${tableName} where ${pkColumn.columnName} in 
+        <foreach item="${pkColumn.javaField}" collection="array" open="(" separator="," close=")">
+            #{${pkColumn.javaField}}
+        </foreach>
+    </delete>
+#if($table.sub)
+    
+    <delete id="delete${subClassName}By${subTableFkClassName}s" parameterType="String">
+        delete from ${subTableName} where ${subTableFkName} in 
+        <foreach item="${subTableFkclassName}" collection="array" open="(" separator="," close=")">
+            #{${subTableFkclassName}}
+        </foreach>
+    </delete>
+
+    <delete id="delete${subClassName}By${subTableFkClassName}" parameterType="${pkColumn.javaType}">
+        delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}}
+    </delete>
+
+    <insert id="batch${subClassName}">
+        insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values
+        <foreach item="item" index="index" collection="list" separator=",">
+            (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end)
+        </foreach>
+    </insert>
+#end
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-quartz/pom.xml b/ruoyi-quartz/pom.xml
new file mode 100644
index 0000000..4e7a0ce
--- /dev/null
+++ b/ruoyi-quartz/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.8.8</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-quartz</artifactId>
+
+    <description>
+        quartz定时任务
+    </description>
+
+    <dependencies>
+
+        <!-- 定时任务 -->
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.mchange</groupId>
+                    <artifactId>c3p0</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- 通用工具-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java
new file mode 100644
index 0000000..d4e065a
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java
@@ -0,0 +1,57 @@
+//package com.ruoyi.quartz.config;
+//
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+//import javax.sql.DataSource;
+//import java.util.Properties;
+//
+///**
+// * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效)
+// * 
+// * @author ruoyi
+// */
+//@Configuration
+//public class ScheduleConfig
+//{
+//    @Bean
+//    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
+//    {
+//        SchedulerFactoryBean factory = new SchedulerFactoryBean();
+//        factory.setDataSource(dataSource);
+//
+//        // quartz参数
+//        Properties prop = new Properties();
+//        prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");
+//        prop.put("org.quartz.scheduler.instanceId", "AUTO");
+//        // 线程池配置
+//        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
+//        prop.put("org.quartz.threadPool.threadCount", "20");
+//        prop.put("org.quartz.threadPool.threadPriority", "5");
+//        // JobStore配置
+//        prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
+//        // 集群配置
+//        prop.put("org.quartz.jobStore.isClustered", "true");
+//        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
+//        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10");
+//        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
+//
+//        // sqlserver 启用
+//        // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
+//        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
+//        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
+//        factory.setQuartzProperties(prop);
+//
+//        factory.setSchedulerName("RuoyiScheduler");
+//        // 延时启动
+//        factory.setStartupDelay(1);
+//        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
+//        // 可选,QuartzScheduler
+//        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
+//        factory.setOverwriteExistingJobs(true);
+//        // 设置自动启动,默认为true
+//        factory.setAutoStartup(true);
+//
+//        return factory;
+//    }
+//}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java
new file mode 100644
index 0000000..e826dfd
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java
@@ -0,0 +1,185 @@
+package com.ruoyi.quartz.controller;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.exception.job.TaskException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.quartz.domain.SysJob;
+import com.ruoyi.quartz.service.ISysJobService;
+import com.ruoyi.quartz.util.CronUtils;
+import com.ruoyi.quartz.util.ScheduleUtils;
+
+/**
+ * 调度任务信息操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/job")
+public class SysJobController extends BaseController
+{
+    @Autowired
+    private ISysJobService jobService;
+
+    /**
+     * 查询定时任务列表
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysJob sysJob)
+    {
+        startPage();
+        List<SysJob> list = jobService.selectJobList(sysJob);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出定时任务列表
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:export')")
+    @Log(title = "定时任务", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysJob sysJob)
+    {
+        List<SysJob> list = jobService.selectJobList(sysJob);
+        ExcelUtil<SysJob> util = new ExcelUtil<SysJob>(SysJob.class);
+        util.exportExcel(response, list, "定时任务");
+    }
+
+    /**
+     * 获取定时任务详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:query')")
+    @GetMapping(value = "/{jobId}")
+    public AjaxResult getInfo(@PathVariable("jobId") Long jobId)
+    {
+        return success(jobService.selectJobById(jobId));
+    }
+
+    /**
+     * 新增定时任务
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:add')")
+    @Log(title = "定时任务", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException
+    {
+        if (!CronUtils.isValid(job.getCronExpression()))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
+        }
+        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
+        }
+        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
+        {
+            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
+        }
+        job.setCreateBy(getUsername());
+        return toAjax(jobService.insertJob(job));
+    }
+
+    /**
+     * 修改定时任务
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:edit')")
+    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException
+    {
+        if (!CronUtils.isValid(job.getCronExpression()))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确");
+        }
+        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+        }
+        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规");
+        }
+        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
+        {
+            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
+        }
+        job.setUpdateBy(getUsername());
+        return toAjax(jobService.updateJob(job));
+    }
+
+    /**
+     * 定时任务状态修改
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')")
+    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException
+    {
+        SysJob newJob = jobService.selectJobById(job.getJobId());
+        newJob.setStatus(job.getStatus());
+        return toAjax(jobService.changeStatus(newJob));
+    }
+
+    /**
+     * 定时任务立即执行一次
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')")
+    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
+    @PutMapping("/run")
+    public AjaxResult run(@RequestBody SysJob job) throws SchedulerException
+    {
+        boolean result = jobService.run(job);
+        return result ? success() : error("任务不存在或已过期!");
+    }
+
+    /**
+     * 删除定时任务
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:remove')")
+    @Log(title = "定时任务", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{jobIds}")
+    public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException
+    {
+        jobService.deleteJobByIds(jobIds);
+        return success();
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java
new file mode 100644
index 0000000..233361b
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java
@@ -0,0 +1,92 @@
+package com.ruoyi.quartz.controller;
+
+import java.util.List;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.quartz.domain.SysJobLog;
+import com.ruoyi.quartz.service.ISysJobLogService;
+
+/**
+ * 调度日志操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/jobLog")
+public class SysJobLogController extends BaseController
+{
+    @Autowired
+    private ISysJobLogService jobLogService;
+
+    /**
+     * 查询定时任务调度日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysJobLog sysJobLog)
+    {
+        startPage();
+        List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出定时任务调度日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:export')")
+    @Log(title = "任务调度日志", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysJobLog sysJobLog)
+    {
+        List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
+        ExcelUtil<SysJobLog> util = new ExcelUtil<SysJobLog>(SysJobLog.class);
+        util.exportExcel(response, list, "调度日志");
+    }
+    
+    /**
+     * 根据调度编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:query')")
+    @GetMapping(value = "/{jobLogId}")
+    public AjaxResult getInfo(@PathVariable Long jobLogId)
+    {
+        return success(jobLogService.selectJobLogById(jobLogId));
+    }
+
+
+    /**
+     * 删除定时任务调度日志
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:remove')")
+    @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{jobLogIds}")
+    public AjaxResult remove(@PathVariable Long[] jobLogIds)
+    {
+        return toAjax(jobLogService.deleteJobLogByIds(jobLogIds));
+    }
+
+    /**
+     * 清空定时任务调度日志
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:remove')")
+    @Log(title = "调度日志", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/clean")
+    public AjaxResult clean()
+    {
+        jobLogService.cleanJobLog();
+        return success();
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java
new file mode 100644
index 0000000..cea12dc
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java
@@ -0,0 +1,171 @@
+package com.ruoyi.quartz.domain;
+
+import java.util.Date;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.constant.ScheduleConstants;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.quartz.util.CronUtils;
+
+/**
+ * 定时任务调度表 sys_job
+ * 
+ * @author ruoyi
+ */
+public class SysJob extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 任务ID */
+    @Excel(name = "任务序号", cellType = ColumnType.NUMERIC)
+    private Long jobId;
+
+    /** 任务名称 */
+    @Excel(name = "任务名称")
+    private String jobName;
+
+    /** 任务组名 */
+    @Excel(name = "任务组名")
+    private String jobGroup;
+
+    /** 调用目标字符串 */
+    @Excel(name = "调用目标字符串")
+    private String invokeTarget;
+
+    /** cron执行表达式 */
+    @Excel(name = "执行表达式 ")
+    private String cronExpression;
+
+    /** cron计划策略 */
+    @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行")
+    private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
+
+    /** 是否并发执行(0允许 1禁止) */
+    @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止")
+    private String concurrent;
+
+    /** 任务状态(0正常 1暂停) */
+    @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停")
+    private String status;
+
+    public Long getJobId()
+    {
+        return jobId;
+    }
+
+    public void setJobId(Long jobId)
+    {
+        this.jobId = jobId;
+    }
+
+    @NotBlank(message = "任务名称不能为空")
+    @Size(min = 0, max = 64, message = "任务名称不能超过64个字符")
+    public String getJobName()
+    {
+        return jobName;
+    }
+
+    public void setJobName(String jobName)
+    {
+        this.jobName = jobName;
+    }
+
+    public String getJobGroup()
+    {
+        return jobGroup;
+    }
+
+    public void setJobGroup(String jobGroup)
+    {
+        this.jobGroup = jobGroup;
+    }
+
+    @NotBlank(message = "调用目标字符串不能为空")
+    @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符")
+    public String getInvokeTarget()
+    {
+        return invokeTarget;
+    }
+
+    public void setInvokeTarget(String invokeTarget)
+    {
+        this.invokeTarget = invokeTarget;
+    }
+
+    @NotBlank(message = "Cron执行表达式不能为空")
+    @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符")
+    public String getCronExpression()
+    {
+        return cronExpression;
+    }
+
+    public void setCronExpression(String cronExpression)
+    {
+        this.cronExpression = cronExpression;
+    }
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    public Date getNextValidTime()
+    {
+        if (StringUtils.isNotEmpty(cronExpression))
+        {
+            return CronUtils.getNextExecution(cronExpression);
+        }
+        return null;
+    }
+
+    public String getMisfirePolicy()
+    {
+        return misfirePolicy;
+    }
+
+    public void setMisfirePolicy(String misfirePolicy)
+    {
+        this.misfirePolicy = misfirePolicy;
+    }
+
+    public String getConcurrent()
+    {
+        return concurrent;
+    }
+
+    public void setConcurrent(String concurrent)
+    {
+        this.concurrent = concurrent;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("jobId", getJobId())
+            .append("jobName", getJobName())
+            .append("jobGroup", getJobGroup())
+            .append("cronExpression", getCronExpression())
+            .append("nextValidTime", getNextValidTime())
+            .append("misfirePolicy", getMisfirePolicy())
+            .append("concurrent", getConcurrent())
+            .append("status", getStatus())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java
new file mode 100644
index 0000000..121c035
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java
@@ -0,0 +1,155 @@
+package com.ruoyi.quartz.domain;
+
+import java.util.Date;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 定时任务调度日志表 sys_job_log
+ * 
+ * @author ruoyi
+ */
+public class SysJobLog extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    @Excel(name = "日志序号")
+    private Long jobLogId;
+
+    /** 任务名称 */
+    @Excel(name = "任务名称")
+    private String jobName;
+
+    /** 任务组名 */
+    @Excel(name = "任务组名")
+    private String jobGroup;
+
+    /** 调用目标字符串 */
+    @Excel(name = "调用目标字符串")
+    private String invokeTarget;
+
+    /** 日志信息 */
+    @Excel(name = "日志信息")
+    private String jobMessage;
+
+    /** 执行状态(0正常 1失败) */
+    @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败")
+    private String status;
+
+    /** 异常信息 */
+    @Excel(name = "异常信息")
+    private String exceptionInfo;
+
+    /** 开始时间 */
+    private Date startTime;
+
+    /** 停止时间 */
+    private Date stopTime;
+
+    public Long getJobLogId()
+    {
+        return jobLogId;
+    }
+
+    public void setJobLogId(Long jobLogId)
+    {
+        this.jobLogId = jobLogId;
+    }
+
+    public String getJobName()
+    {
+        return jobName;
+    }
+
+    public void setJobName(String jobName)
+    {
+        this.jobName = jobName;
+    }
+
+    public String getJobGroup()
+    {
+        return jobGroup;
+    }
+
+    public void setJobGroup(String jobGroup)
+    {
+        this.jobGroup = jobGroup;
+    }
+
+    public String getInvokeTarget()
+    {
+        return invokeTarget;
+    }
+
+    public void setInvokeTarget(String invokeTarget)
+    {
+        this.invokeTarget = invokeTarget;
+    }
+
+    public String getJobMessage()
+    {
+        return jobMessage;
+    }
+
+    public void setJobMessage(String jobMessage)
+    {
+        this.jobMessage = jobMessage;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public String getExceptionInfo()
+    {
+        return exceptionInfo;
+    }
+
+    public void setExceptionInfo(String exceptionInfo)
+    {
+        this.exceptionInfo = exceptionInfo;
+    }
+
+    public Date getStartTime()
+    {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime)
+    {
+        this.startTime = startTime;
+    }
+    
+    public Date getStopTime()
+    {
+        return stopTime;
+    }
+
+    public void setStopTime(Date stopTime)
+    {
+        this.stopTime = stopTime;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("jobLogId", getJobLogId())
+            .append("jobName", getJobName())
+            .append("jobGroup", getJobGroup())
+            .append("jobMessage", getJobMessage())
+            .append("status", getStatus())
+            .append("exceptionInfo", getExceptionInfo())
+            .append("startTime", getStartTime())
+            .append("stopTime", getStopTime())
+            .toString();
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java
new file mode 100644
index 0000000..727d916
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java
@@ -0,0 +1,64 @@
+package com.ruoyi.quartz.mapper;
+
+import java.util.List;
+import com.ruoyi.quartz.domain.SysJobLog;
+
+/**
+ * 调度任务日志信息 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysJobLogMapper
+{
+    /**
+     * 获取quartz调度器日志的计划任务
+     * 
+     * @param jobLog 调度日志信息
+     * @return 调度任务日志集合
+     */
+    public List<SysJobLog> selectJobLogList(SysJobLog jobLog);
+
+    /**
+     * 查询所有调度任务日志
+     *
+     * @return 调度任务日志列表
+     */
+    public List<SysJobLog> selectJobLogAll();
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     * 
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    public SysJobLog selectJobLogById(Long jobLogId);
+
+    /**
+     * 新增任务日志
+     * 
+     * @param jobLog 调度日志信息
+     * @return 结果
+     */
+    public int insertJobLog(SysJobLog jobLog);
+
+    /**
+     * 批量删除调度日志信息
+     * 
+     * @param logIds 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteJobLogByIds(Long[] logIds);
+
+    /**
+     * 删除任务日志
+     * 
+     * @param jobId 调度日志ID
+     * @return 结果
+     */
+    public int deleteJobLogById(Long jobId);
+
+    /**
+     * 清空任务日志
+     */
+    public void cleanJobLog();
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java
new file mode 100644
index 0000000..20f45db
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java
@@ -0,0 +1,67 @@
+package com.ruoyi.quartz.mapper;
+
+import java.util.List;
+import com.ruoyi.quartz.domain.SysJob;
+
+/**
+ * 调度任务信息 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysJobMapper
+{
+    /**
+     * 查询调度任务日志集合
+     * 
+     * @param job 调度信息
+     * @return 操作日志集合
+     */
+    public List<SysJob> selectJobList(SysJob job);
+
+    /**
+     * 查询所有调度任务
+     * 
+     * @return 调度任务列表
+     */
+    public List<SysJob> selectJobAll();
+
+    /**
+     * 通过调度ID查询调度任务信息
+     * 
+     * @param jobId 调度ID
+     * @return 角色对象信息
+     */
+    public SysJob selectJobById(Long jobId);
+
+    /**
+     * 通过调度ID删除调度任务信息
+     * 
+     * @param jobId 调度ID
+     * @return 结果
+     */
+    public int deleteJobById(Long jobId);
+
+    /**
+     * 批量删除调度任务信息
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteJobByIds(Long[] ids);
+
+    /**
+     * 修改调度任务信息
+     * 
+     * @param job 调度任务信息
+     * @return 结果
+     */
+    public int updateJob(SysJob job);
+
+    /**
+     * 新增调度任务信息
+     * 
+     * @param job 调度任务信息
+     * @return 结果
+     */
+    public int insertJob(SysJob job);
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java
new file mode 100644
index 0000000..8546792
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java
@@ -0,0 +1,56 @@
+package com.ruoyi.quartz.service;
+
+import java.util.List;
+import com.ruoyi.quartz.domain.SysJobLog;
+
+/**
+ * 定时任务调度日志信息信息 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysJobLogService
+{
+    /**
+     * 获取quartz调度器日志的计划任务
+     * 
+     * @param jobLog 调度日志信息
+     * @return 调度任务日志集合
+     */
+    public List<SysJobLog> selectJobLogList(SysJobLog jobLog);
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     * 
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    public SysJobLog selectJobLogById(Long jobLogId);
+
+    /**
+     * 新增任务日志
+     * 
+     * @param jobLog 调度日志信息
+     */
+    public void addJobLog(SysJobLog jobLog);
+
+    /**
+     * 批量删除调度日志信息
+     * 
+     * @param logIds 需要删除的日志ID
+     * @return 结果
+     */
+    public int deleteJobLogByIds(Long[] logIds);
+
+    /**
+     * 删除任务日志
+     * 
+     * @param jobId 调度日志ID
+     * @return 结果
+     */
+    public int deleteJobLogById(Long jobId);
+
+    /**
+     * 清空任务日志
+     */
+    public void cleanJobLog();
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java
new file mode 100644
index 0000000..437ade8
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java
@@ -0,0 +1,102 @@
+package com.ruoyi.quartz.service;
+
+import java.util.List;
+import org.quartz.SchedulerException;
+import com.ruoyi.common.exception.job.TaskException;
+import com.ruoyi.quartz.domain.SysJob;
+
+/**
+ * 定时任务调度信息信息 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysJobService
+{
+    /**
+     * 获取quartz调度器的计划任务
+     * 
+     * @param job 调度信息
+     * @return 调度任务集合
+     */
+    public List<SysJob> selectJobList(SysJob job);
+
+    /**
+     * 通过调度任务ID查询调度信息
+     * 
+     * @param jobId 调度任务ID
+     * @return 调度任务对象信息
+     */
+    public SysJob selectJobById(Long jobId);
+
+    /**
+     * 暂停任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int pauseJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 恢复任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int resumeJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 删除任务后,所对应的trigger也将被删除
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int deleteJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 批量删除调度信息
+     * 
+     * @param jobIds 需要删除的任务ID
+     * @return 结果
+     */
+    public void deleteJobByIds(Long[] jobIds) throws SchedulerException;
+
+    /**
+     * 任务调度状态修改
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int changeStatus(SysJob job) throws SchedulerException;
+
+    /**
+     * 立即运行任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public boolean run(SysJob job) throws SchedulerException;
+
+    /**
+     * 新增任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int insertJob(SysJob job) throws SchedulerException, TaskException;
+
+    /**
+     * 更新任务
+     * 
+     * @param job 调度信息
+     * @return 结果
+     */
+    public int updateJob(SysJob job) throws SchedulerException, TaskException;
+
+    /**
+     * 校验cron表达式是否有效
+     * 
+     * @param cronExpression 表达式
+     * @return 结果
+     */
+    public boolean checkCronExpressionIsValid(String cronExpression);
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java
new file mode 100644
index 0000000..812eed7
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java
@@ -0,0 +1,87 @@
+package com.ruoyi.quartz.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.quartz.domain.SysJobLog;
+import com.ruoyi.quartz.mapper.SysJobLogMapper;
+import com.ruoyi.quartz.service.ISysJobLogService;
+
+/**
+ * 定时任务调度日志信息 服务层
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysJobLogServiceImpl implements ISysJobLogService
+{
+    @Autowired
+    private SysJobLogMapper jobLogMapper;
+
+    /**
+     * 获取quartz调度器日志的计划任务
+     * 
+     * @param jobLog 调度日志信息
+     * @return 调度任务日志集合
+     */
+    @Override
+    public List<SysJobLog> selectJobLogList(SysJobLog jobLog)
+    {
+        return jobLogMapper.selectJobLogList(jobLog);
+    }
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     * 
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    @Override
+    public SysJobLog selectJobLogById(Long jobLogId)
+    {
+        return jobLogMapper.selectJobLogById(jobLogId);
+    }
+
+    /**
+     * 新增任务日志
+     * 
+     * @param jobLog 调度日志信息
+     */
+    @Override
+    public void addJobLog(SysJobLog jobLog)
+    {
+        jobLogMapper.insertJobLog(jobLog);
+    }
+
+    /**
+     * 批量删除调度日志信息
+     * 
+     * @param logIds 需要删除的数据ID
+     * @return 结果
+     */
+    @Override
+    public int deleteJobLogByIds(Long[] logIds)
+    {
+        return jobLogMapper.deleteJobLogByIds(logIds);
+    }
+
+    /**
+     * 删除任务日志
+     * 
+     * @param jobId 调度日志ID
+     */
+    @Override
+    public int deleteJobLogById(Long jobId)
+    {
+        return jobLogMapper.deleteJobLogById(jobId);
+    }
+
+    /**
+     * 清空任务日志
+     */
+    @Override
+    public void cleanJobLog()
+    {
+        jobLogMapper.cleanJobLog();
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java
new file mode 100644
index 0000000..78ebef8
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java
@@ -0,0 +1,261 @@
+package com.ruoyi.quartz.service.impl;
+
+import java.util.List;
+import jakarta.annotation.PostConstruct;
+import org.quartz.JobDataMap;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.ruoyi.common.constant.ScheduleConstants;
+import com.ruoyi.common.exception.job.TaskException;
+import com.ruoyi.quartz.domain.SysJob;
+import com.ruoyi.quartz.mapper.SysJobMapper;
+import com.ruoyi.quartz.service.ISysJobService;
+import com.ruoyi.quartz.util.CronUtils;
+import com.ruoyi.quartz.util.ScheduleUtils;
+
+/**
+ * 定时任务调度信息 服务层
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysJobServiceImpl implements ISysJobService
+{
+    @Autowired
+    private Scheduler scheduler;
+
+    @Autowired
+    private SysJobMapper jobMapper;
+
+    /**
+     * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
+     */
+    @PostConstruct
+    public void init() throws SchedulerException, TaskException
+    {
+        scheduler.clear();
+        List<SysJob> jobList = jobMapper.selectJobAll();
+        for (SysJob job : jobList)
+        {
+            ScheduleUtils.createScheduleJob(scheduler, job);
+        }
+    }
+
+    /**
+     * 获取quartz调度器的计划任务列表
+     * 
+     * @param job 调度信息
+     * @return
+     */
+    @Override
+    public List<SysJob> selectJobList(SysJob job)
+    {
+        return jobMapper.selectJobList(job);
+    }
+
+    /**
+     * 通过调度任务ID查询调度信息
+     * 
+     * @param jobId 调度任务ID
+     * @return 调度任务对象信息
+     */
+    @Override
+    public SysJob selectJobById(Long jobId)
+    {
+        return jobMapper.selectJobById(jobId);
+    }
+
+    /**
+     * 暂停任务
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int pauseJob(SysJob job) throws SchedulerException
+    {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
+        int rows = jobMapper.updateJob(job);
+        if (rows > 0)
+        {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+        return rows;
+    }
+
+    /**
+     * 恢复任务
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int resumeJob(SysJob job) throws SchedulerException
+    {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
+        int rows = jobMapper.updateJob(job);
+        if (rows > 0)
+        {
+            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+        return rows;
+    }
+
+    /**
+     * 删除任务后,所对应的trigger也将被删除
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteJob(SysJob job) throws SchedulerException
+    {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        int rows = jobMapper.deleteJobById(jobId);
+        if (rows > 0)
+        {
+            scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+        return rows;
+    }
+
+    /**
+     * 批量删除调度信息
+     * 
+     * @param jobIds 需要删除的任务ID
+     * @return 结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteJobByIds(Long[] jobIds) throws SchedulerException
+    {
+        for (Long jobId : jobIds)
+        {
+            SysJob job = jobMapper.selectJobById(jobId);
+            deleteJob(job);
+        }
+    }
+
+    /**
+     * 任务调度状态修改
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int changeStatus(SysJob job) throws SchedulerException
+    {
+        int rows = 0;
+        String status = job.getStatus();
+        if (ScheduleConstants.Status.NORMAL.getValue().equals(status))
+        {
+            rows = resumeJob(job);
+        }
+        else if (ScheduleConstants.Status.PAUSE.getValue().equals(status))
+        {
+            rows = pauseJob(job);
+        }
+        return rows;
+    }
+
+    /**
+     * 立即运行任务
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean run(SysJob job) throws SchedulerException
+    {
+        boolean result = false;
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        SysJob properties = selectJobById(job.getJobId());
+        // 参数
+        JobDataMap dataMap = new JobDataMap();
+        dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties);
+        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
+        if (scheduler.checkExists(jobKey))
+        {
+            result = true;
+            scheduler.triggerJob(jobKey, dataMap);
+        }
+        return result;
+    }
+
+    /**
+     * 新增任务
+     * 
+     * @param job 调度信息 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int insertJob(SysJob job) throws SchedulerException, TaskException
+    {
+        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
+        int rows = jobMapper.insertJob(job);
+        if (rows > 0)
+        {
+            ScheduleUtils.createScheduleJob(scheduler, job);
+        }
+        return rows;
+    }
+
+    /**
+     * 更新任务的时间表达式
+     * 
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int updateJob(SysJob job) throws SchedulerException, TaskException
+    {
+        SysJob properties = selectJobById(job.getJobId());
+        int rows = jobMapper.updateJob(job);
+        if (rows > 0)
+        {
+            updateSchedulerJob(job, properties.getJobGroup());
+        }
+        return rows;
+    }
+
+    /**
+     * 更新任务
+     * 
+     * @param job 任务对象
+     * @param jobGroup 任务组名
+     */
+    public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException
+    {
+        Long jobId = job.getJobId();
+        // 判断是否存在
+        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
+        if (scheduler.checkExists(jobKey))
+        {
+            // 防止创建时存在数据问题 先移除,然后在执行创建操作
+            scheduler.deleteJob(jobKey);
+        }
+        ScheduleUtils.createScheduleJob(scheduler, job);
+    }
+
+    /**
+     * 校验cron表达式是否有效
+     * 
+     * @param cronExpression 表达式
+     * @return 结果
+     */
+    @Override
+    public boolean checkCronExpressionIsValid(String cronExpression)
+    {
+        return CronUtils.isValid(cronExpression);
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
new file mode 100644
index 0000000..853243b
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -0,0 +1,28 @@
+package com.ruoyi.quartz.task;
+
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 定时任务调度测试
+ * 
+ * @author ruoyi
+ */
+@Component("ryTask")
+public class RyTask
+{
+    public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i)
+    {
+        System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
+    }
+
+    public void ryParams(String params)
+    {
+        System.out.println("执行有参方法:" + params);
+    }
+
+    public void ryNoParams()
+    {
+        System.out.println("执行无参方法");
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java
new file mode 100644
index 0000000..731a5eb
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java
@@ -0,0 +1,107 @@
+package com.ruoyi.quartz.util;
+
+import java.util.Date;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.ScheduleConstants;
+import com.ruoyi.common.utils.ExceptionUtil;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.quartz.domain.SysJob;
+import com.ruoyi.quartz.domain.SysJobLog;
+import com.ruoyi.quartz.service.ISysJobLogService;
+
+/**
+ * 抽象quartz调用
+ *
+ * @author ruoyi
+ */
+public abstract class AbstractQuartzJob implements Job
+{
+    private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
+
+    /**
+     * 线程本地变量
+     */
+    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
+
+    @Override
+    public void execute(JobExecutionContext context) throws JobExecutionException
+    {
+        SysJob sysJob = new SysJob();
+        BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
+        try
+        {
+            before(context, sysJob);
+            if (sysJob != null)
+            {
+                doExecute(context, sysJob);
+            }
+            after(context, sysJob, null);
+        }
+        catch (Exception e)
+        {
+            log.error("任务执行异常  - :", e);
+            after(context, sysJob, e);
+        }
+    }
+
+    /**
+     * 执行前
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob 系统计划任务
+     */
+    protected void before(JobExecutionContext context, SysJob sysJob)
+    {
+        threadLocal.set(new Date());
+    }
+
+    /**
+     * 执行后
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob 系统计划任务
+     */
+    protected void after(JobExecutionContext context, SysJob sysJob, Exception e)
+    {
+        Date startTime = threadLocal.get();
+        threadLocal.remove();
+
+        final SysJobLog sysJobLog = new SysJobLog();
+        sysJobLog.setJobName(sysJob.getJobName());
+        sysJobLog.setJobGroup(sysJob.getJobGroup());
+        sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
+        sysJobLog.setStartTime(startTime);
+        sysJobLog.setStopTime(new Date());
+        long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
+        sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
+        if (e != null)
+        {
+            sysJobLog.setStatus(Constants.FAIL);
+            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
+            sysJobLog.setExceptionInfo(errorMsg);
+        }
+        else
+        {
+            sysJobLog.setStatus(Constants.SUCCESS);
+        }
+
+        // 写入数据库当中
+        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
+    }
+
+    /**
+     * 执行方法,由子类重载
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob 系统计划任务
+     * @throws Exception 执行过程中的异常
+     */
+    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java
new file mode 100644
index 0000000..dd53839
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java
@@ -0,0 +1,63 @@
+package com.ruoyi.quartz.util;
+
+import java.text.ParseException;
+import java.util.Date;
+import org.quartz.CronExpression;
+
+/**
+ * cron表达式工具类
+ * 
+ * @author ruoyi
+ *
+ */
+public class CronUtils
+{
+    /**
+     * 返回一个布尔值代表一个给定的Cron表达式的有效性
+     *
+     * @param cronExpression Cron表达式
+     * @return boolean 表达式是否有效
+     */
+    public static boolean isValid(String cronExpression)
+    {
+        return CronExpression.isValidExpression(cronExpression);
+    }
+
+    /**
+     * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
+     *
+     * @param cronExpression Cron表达式
+     * @return String 无效时返回表达式错误描述,如果有效返回null
+     */
+    public static String getInvalidMessage(String cronExpression)
+    {
+        try
+        {
+            new CronExpression(cronExpression);
+            return null;
+        }
+        catch (ParseException pe)
+        {
+            return pe.getMessage();
+        }
+    }
+
+    /**
+     * 返回下一个执行时间根据给定的Cron表达式
+     *
+     * @param cronExpression Cron表达式
+     * @return Date 下次Cron表达式执行时间
+     */
+    public static Date getNextExecution(String cronExpression)
+    {
+        try
+        {
+            CronExpression cron = new CronExpression(cronExpression);
+            return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
+        }
+        catch (ParseException e)
+        {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java
new file mode 100644
index 0000000..e3dc62c
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java
@@ -0,0 +1,182 @@
+package com.ruoyi.quartz.util;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.quartz.domain.SysJob;
+
+/**
+ * 任务执行工具
+ *
+ * @author ruoyi
+ */
+public class JobInvokeUtil
+{
+    /**
+     * 执行方法
+     *
+     * @param sysJob 系统任务
+     */
+    public static void invokeMethod(SysJob sysJob) throws Exception
+    {
+        String invokeTarget = sysJob.getInvokeTarget();
+        String beanName = getBeanName(invokeTarget);
+        String methodName = getMethodName(invokeTarget);
+        List<Object[]> methodParams = getMethodParams(invokeTarget);
+
+        if (!isValidClassName(beanName))
+        {
+            Object bean = SpringUtils.getBean(beanName);
+            invokeMethod(bean, methodName, methodParams);
+        }
+        else
+        {
+            Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
+            invokeMethod(bean, methodName, methodParams);
+        }
+    }
+
+    /**
+     * 调用任务方法
+     *
+     * @param bean 目标对象
+     * @param methodName 方法名称
+     * @param methodParams 方法参数
+     */
+    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
+            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
+            InvocationTargetException
+    {
+        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
+        {
+            Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
+            method.invoke(bean, getMethodParamsValue(methodParams));
+        }
+        else
+        {
+            Method method = bean.getClass().getMethod(methodName);
+            method.invoke(bean);
+        }
+    }
+
+    /**
+     * 校验是否为为class包名
+     * 
+     * @param invokeTarget 名称
+     * @return true是 false否
+     */
+    public static boolean isValidClassName(String invokeTarget)
+    {
+        return StringUtils.countMatches(invokeTarget, ".") > 1;
+    }
+
+    /**
+     * 获取bean名称
+     * 
+     * @param invokeTarget 目标字符串
+     * @return bean名称
+     */
+    public static String getBeanName(String invokeTarget)
+    {
+        String beanName = StringUtils.substringBefore(invokeTarget, "(");
+        return StringUtils.substringBeforeLast(beanName, ".");
+    }
+
+    /**
+     * 获取bean方法
+     * 
+     * @param invokeTarget 目标字符串
+     * @return method方法
+     */
+    public static String getMethodName(String invokeTarget)
+    {
+        String methodName = StringUtils.substringBefore(invokeTarget, "(");
+        return StringUtils.substringAfterLast(methodName, ".");
+    }
+
+    /**
+     * 获取method方法参数相关列表
+     * 
+     * @param invokeTarget 目标字符串
+     * @return method方法相关参数列表
+     */
+    public static List<Object[]> getMethodParams(String invokeTarget)
+    {
+        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
+        if (StringUtils.isEmpty(methodStr))
+        {
+            return null;
+        }
+        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
+        List<Object[]> classs = new LinkedList<>();
+        for (int i = 0; i < methodParams.length; i++)
+        {
+            String str = StringUtils.trimToEmpty(methodParams[i]);
+            // String字符串类型,以'或"开头
+            if (StringUtils.startsWithAny(str, "'", "\""))
+            {
+                classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class });
+            }
+            // boolean布尔类型,等于true或者false
+            else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str))
+            {
+                classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
+            }
+            // long长整形,以L结尾
+            else if (StringUtils.endsWith(str, "L"))
+            {
+                classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class });
+            }
+            // double浮点类型,以D结尾
+            else if (StringUtils.endsWith(str, "D"))
+            {
+                classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class });
+            }
+            // 其他类型归类为整形
+            else
+            {
+                classs.add(new Object[] { Integer.valueOf(str), Integer.class });
+            }
+        }
+        return classs;
+    }
+
+    /**
+     * 获取参数类型
+     * 
+     * @param methodParams 参数相关列表
+     * @return 参数类型列表
+     */
+    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
+    {
+        Class<?>[] classs = new Class<?>[methodParams.size()];
+        int index = 0;
+        for (Object[] os : methodParams)
+        {
+            classs[index] = (Class<?>) os[1];
+            index++;
+        }
+        return classs;
+    }
+
+    /**
+     * 获取参数值
+     * 
+     * @param methodParams 参数相关列表
+     * @return 参数值列表
+     */
+    public static Object[] getMethodParamsValue(List<Object[]> methodParams)
+    {
+        Object[] classs = new Object[methodParams.size()];
+        int index = 0;
+        for (Object[] os : methodParams)
+        {
+            classs[index] = (Object) os[0];
+            index++;
+        }
+        return classs;
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java
new file mode 100644
index 0000000..5e13558
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java
@@ -0,0 +1,21 @@
+package com.ruoyi.quartz.util;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+import com.ruoyi.quartz.domain.SysJob;
+
+/**
+ * 定时任务处理(禁止并发执行)
+ * 
+ * @author ruoyi
+ *
+ */
+@DisallowConcurrentExecution
+public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
+{
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
+    {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java
new file mode 100644
index 0000000..e975326
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java
@@ -0,0 +1,19 @@
+package com.ruoyi.quartz.util;
+
+import org.quartz.JobExecutionContext;
+import com.ruoyi.quartz.domain.SysJob;
+
+/**
+ * 定时任务处理(允许并发执行)
+ * 
+ * @author ruoyi
+ *
+ */
+public class QuartzJobExecution extends AbstractQuartzJob
+{
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
+    {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java
new file mode 100644
index 0000000..21fedae
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java
@@ -0,0 +1,141 @@
+package com.ruoyi.quartz.util;
+
+import org.quartz.CronScheduleBuilder;
+import org.quartz.CronTrigger;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.TriggerBuilder;
+import org.quartz.TriggerKey;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.ScheduleConstants;
+import com.ruoyi.common.exception.job.TaskException;
+import com.ruoyi.common.exception.job.TaskException.Code;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.quartz.domain.SysJob;
+
+/**
+ * 定时任务工具类
+ * 
+ * @author ruoyi
+ *
+ */
+public class ScheduleUtils
+{
+    /**
+     * 得到quartz任务类
+     *
+     * @param sysJob 执行计划
+     * @return 具体执行任务类
+     */
+    private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
+    {
+        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
+        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
+    }
+
+    /**
+     * 构建任务触发对象
+     */
+    public static TriggerKey getTriggerKey(Long jobId, String jobGroup)
+    {
+        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 构建任务键对象
+     */
+    public static JobKey getJobKey(Long jobId, String jobGroup)
+    {
+        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 创建定时任务
+     */
+    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
+    {
+        Class<? extends Job> jobClass = getQuartzJobClass(job);
+        // 构建job信息
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
+
+        // 表达式调度构建器
+        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
+        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
+
+        // 按新的cronExpression表达式构建一个新的trigger
+        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
+                .withSchedule(cronScheduleBuilder).build();
+
+        // 放入参数,运行时的方法可以获取
+        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
+
+        // 判断是否存在
+        if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
+        {
+            // 防止创建时存在数据问题 先移除,然后在执行创建操作
+            scheduler.deleteJob(getJobKey(jobId, jobGroup));
+        }
+
+        // 判断任务是否过期
+        if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
+        {
+            // 执行调度任务
+            scheduler.scheduleJob(jobDetail, trigger);
+        }
+
+        // 暂停任务
+        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
+        {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+    }
+
+    /**
+     * 设置定时任务策略
+     */
+    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
+            throws TaskException
+    {
+        switch (job.getMisfirePolicy())
+        {
+            case ScheduleConstants.MISFIRE_DEFAULT:
+                return cb;
+            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
+                return cb.withMisfireHandlingInstructionIgnoreMisfires();
+            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
+                return cb.withMisfireHandlingInstructionFireAndProceed();
+            case ScheduleConstants.MISFIRE_DO_NOTHING:
+                return cb.withMisfireHandlingInstructionDoNothing();
+            default:
+                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+                        + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
+        }
+    }
+
+    /**
+     * 检查包名是否为白名单配置
+     * 
+     * @param invokeTarget 目标字符串
+     * @return 结果
+     */
+    public static boolean whiteList(String invokeTarget)
+    {
+        String packageName = StringUtils.substringBefore(invokeTarget, "(");
+        int count = StringUtils.countMatches(packageName, ".");
+        if (count > 1)
+        {
+            return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR);
+        }
+        Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]);
+        String beanPackageName = obj.getClass().getPackage().getName();
+        return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR)
+                && !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR);
+    }
+}
diff --git a/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml
new file mode 100644
index 0000000..ba1b683
--- /dev/null
+++ b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.quartz.mapper.SysJobLogMapper">
+
+	<resultMap type="SysJobLog" id="SysJobLogResult">
+		<id     property="jobLogId"       column="job_log_id"      />
+		<result property="jobName"        column="job_name"        />
+		<result property="jobGroup"       column="job_group"       />
+		<result property="invokeTarget"   column="invoke_target"   />
+		<result property="jobMessage"     column="job_message"     />
+		<result property="status"         column="status"          />
+		<result property="exceptionInfo"  column="exception_info"  />
+		<result property="createTime"     column="create_time"     />
+	</resultMap>
+	
+	<sql id="selectJobLogVo">
+        select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time 
+		from sys_job_log
+    </sql>
+	
+	<select id="selectJobLogList" parameterType="SysJobLog" resultMap="SysJobLogResult">
+		<include refid="selectJobLogVo"/>
+		<where>
+			<if test="jobName != null and jobName != ''">
+				AND job_name like concat('%', #{jobName}, '%')
+			</if>
+			<if test="jobGroup != null and jobGroup != ''">
+				AND job_group = #{jobGroup}
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+			<if test="invokeTarget != null and invokeTarget != ''">
+				AND invoke_target like concat('%', #{invokeTarget}, '%')
+			</if>
+			<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+				and date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
+			</if>
+			<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+				and date_format(create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
+			</if>
+		</where>
+		order by create_time desc
+	</select>
+	
+	<select id="selectJobLogAll" resultMap="SysJobLogResult">
+		<include refid="selectJobLogVo"/>
+	</select>
+	
+	<select id="selectJobLogById" parameterType="Long" resultMap="SysJobLogResult">
+		<include refid="selectJobLogVo"/>
+		where job_log_id = #{jobLogId}
+	</select>
+	
+	<delete id="deleteJobLogById" parameterType="Long">
+ 		delete from sys_job_log where job_log_id = #{jobLogId}
+ 	</delete>
+ 	
+ 	<delete id="deleteJobLogByIds" parameterType="Long">
+ 		delete from sys_job_log where job_log_id in
+ 		<foreach collection="array" item="jobLogId" open="(" separator="," close=")">
+ 			#{jobLogId}
+        </foreach> 
+ 	</delete>
+ 	
+ 	<update id="cleanJobLog">
+        truncate table sys_job_log
+    </update>
+ 	
+ 	<insert id="insertJobLog" parameterType="SysJobLog">
+ 		insert into sys_job_log(
+ 			<if test="jobLogId != null and jobLogId != 0">job_log_id,</if>
+ 			<if test="jobName != null and jobName != ''">job_name,</if>
+ 			<if test="jobGroup != null and jobGroup != ''">job_group,</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if>
+ 			<if test="jobMessage != null and jobMessage != ''">job_message,</if>
+ 			<if test="status != null and status != ''">status,</if>
+ 			<if test="exceptionInfo != null and exceptionInfo != ''">exception_info,</if>
+ 			create_time
+ 		)values(
+ 			<if test="jobLogId != null and jobLogId != 0">#{jobLogId},</if>
+ 			<if test="jobName != null and jobName != ''">#{jobName},</if>
+ 			<if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if>
+ 			<if test="jobMessage != null and jobMessage != ''">#{jobMessage},</if>
+ 			<if test="status != null and status != ''">#{status},</if>
+ 			<if test="exceptionInfo != null and exceptionInfo != ''">#{exceptionInfo},</if>
+ 			sysdate()
+ 		)
+	</insert>
+
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml
new file mode 100644
index 0000000..5605c44
--- /dev/null
+++ b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.quartz.mapper.SysJobMapper">
+
+	<resultMap type="SysJob" id="SysJobResult">
+		<id     property="jobId"          column="job_id"          />
+		<result property="jobName"        column="job_name"        />
+		<result property="jobGroup"       column="job_group"       />
+		<result property="invokeTarget"   column="invoke_target"   />
+		<result property="cronExpression" column="cron_expression" />
+		<result property="misfirePolicy"  column="misfire_policy"  />
+		<result property="concurrent"     column="concurrent"      />
+		<result property="status"         column="status"          />
+		<result property="createBy"       column="create_by"       />
+		<result property="createTime"     column="create_time"     />
+		<result property="updateBy"       column="update_by"       />
+		<result property="updateTime"     column="update_time"     />
+		<result property="remark"         column="remark"          />
+	</resultMap>
+	
+	<sql id="selectJobVo">
+        select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark 
+		from sys_job
+    </sql>
+	
+	<select id="selectJobList" parameterType="SysJob" resultMap="SysJobResult">
+		<include refid="selectJobVo"/>
+		<where>
+			<if test="jobName != null and jobName != ''">
+				AND job_name like concat('%', #{jobName}, '%')
+			</if>
+			<if test="jobGroup != null and jobGroup != ''">
+				AND job_group = #{jobGroup}
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+			<if test="invokeTarget != null and invokeTarget != ''">
+				AND invoke_target like concat('%', #{invokeTarget}, '%')
+			</if>
+		</where>
+	</select>
+	
+	<select id="selectJobAll" resultMap="SysJobResult">
+		<include refid="selectJobVo"/>
+	</select>
+	
+	<select id="selectJobById" parameterType="Long" resultMap="SysJobResult">
+		<include refid="selectJobVo"/>
+		where job_id = #{jobId}
+	</select>
+	
+	<delete id="deleteJobById" parameterType="Long">
+ 		delete from sys_job where job_id = #{jobId}
+ 	</delete>
+ 	
+ 	<delete id="deleteJobByIds" parameterType="Long">
+ 		delete from sys_job where job_id in
+ 		<foreach collection="array" item="jobId" open="(" separator="," close=")">
+ 			#{jobId}
+        </foreach> 
+ 	</delete>
+ 	
+ 	<update id="updateJob" parameterType="SysJob">
+ 		update sys_job
+ 		<set>
+ 			<if test="jobName != null and jobName != ''">job_name = #{jobName},</if>
+ 			<if test="jobGroup != null and jobGroup != ''">job_group = #{jobGroup},</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">invoke_target = #{invokeTarget},</if>
+ 			<if test="cronExpression != null and cronExpression != ''">cron_expression = #{cronExpression},</if>
+ 			<if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy = #{misfirePolicy},</if>
+ 			<if test="concurrent != null and concurrent != ''">concurrent = #{concurrent},</if>
+ 			<if test="status !=null">status = #{status},</if>
+ 			<if test="remark != null and remark != ''">remark = #{remark},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where job_id = #{jobId}
+	</update>
+ 	
+ 	<insert id="insertJob" parameterType="SysJob" useGeneratedKeys="true" keyProperty="jobId">
+ 		insert into sys_job(
+ 			<if test="jobId != null and jobId != 0">job_id,</if>
+ 			<if test="jobName != null and jobName != ''">job_name,</if>
+ 			<if test="jobGroup != null and jobGroup != ''">job_group,</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if>
+ 			<if test="cronExpression != null and cronExpression != ''">cron_expression,</if>
+ 			<if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy,</if>
+ 			<if test="concurrent != null and concurrent != ''">concurrent,</if>
+ 			<if test="status != null and status != ''">status,</if>
+ 			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			create_time
+ 		)values(
+ 			<if test="jobId != null and jobId != 0">#{jobId},</if>
+ 			<if test="jobName != null and jobName != ''">#{jobName},</if>
+ 			<if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if>
+ 			<if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if>
+ 			<if test="cronExpression != null and cronExpression != ''">#{cronExpression},</if>
+ 			<if test="misfirePolicy != null and misfirePolicy != ''">#{misfirePolicy},</if>
+ 			<if test="concurrent != null and concurrent != ''">#{concurrent},</if>
+ 			<if test="status != null and status != ''">#{status},</if>
+ 			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			sysdate()
+ 		)
+	</insert>
+
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml
new file mode 100644
index 0000000..51f3f3c
--- /dev/null
+++ b/ruoyi-system/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.8.8</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-system</artifactId>
+
+    <description>
+        system系统模块
+    </description>
+
+    <dependencies>
+
+        <!-- 通用工具-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java
new file mode 100644
index 0000000..83f0703
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java
@@ -0,0 +1,81 @@
+package com.ruoyi.system.domain;
+
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 缓存信息
+ * 
+ * @author ruoyi
+ */
+public class SysCache
+{
+    /** 缓存名称 */
+    private String cacheName = "";
+
+    /** 缓存键名 */
+    private String cacheKey = "";
+
+    /** 缓存内容 */
+    private String cacheValue = "";
+
+    /** 备注 */
+    private String remark = "";
+
+    public SysCache()
+    {
+
+    }
+
+    public SysCache(String cacheName, String remark)
+    {
+        this.cacheName = cacheName;
+        this.remark = remark;
+    }
+
+    public SysCache(String cacheName, String cacheKey, String cacheValue)
+    {
+        this.cacheName = StringUtils.replace(cacheName, ":", "");
+        this.cacheKey = StringUtils.replace(cacheKey, cacheName, "");
+        this.cacheValue = cacheValue;
+    }
+
+    public String getCacheName()
+    {
+        return cacheName;
+    }
+
+    public void setCacheName(String cacheName)
+    {
+        this.cacheName = cacheName;
+    }
+
+    public String getCacheKey()
+    {
+        return cacheKey;
+    }
+
+    public void setCacheKey(String cacheKey)
+    {
+        this.cacheKey = cacheKey;
+    }
+
+    public String getCacheValue()
+    {
+        return cacheValue;
+    }
+
+    public void setCacheValue(String cacheValue)
+    {
+        this.cacheValue = cacheValue;
+    }
+
+    public String getRemark()
+    {
+        return remark;
+    }
+
+    public void setRemark(String remark)
+    {
+        this.remark = remark;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java
new file mode 100644
index 0000000..cae07be
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java
@@ -0,0 +1,111 @@
+package com.ruoyi.system.domain;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 参数配置表 sys_config
+ * 
+ * @author ruoyi
+ */
+public class SysConfig extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 参数主键 */
+    @Excel(name = "参数主键", cellType = ColumnType.NUMERIC)
+    private Long configId;
+
+    /** 参数名称 */
+    @Excel(name = "参数名称")
+    private String configName;
+
+    /** 参数键名 */
+    @Excel(name = "参数键名")
+    private String configKey;
+
+    /** 参数键值 */
+    @Excel(name = "参数键值")
+    private String configValue;
+
+    /** 系统内置(Y是 N否) */
+    @Excel(name = "系统内置", readConverterExp = "Y=是,N=否")
+    private String configType;
+
+    public Long getConfigId()
+    {
+        return configId;
+    }
+
+    public void setConfigId(Long configId)
+    {
+        this.configId = configId;
+    }
+
+    @NotBlank(message = "参数名称不能为空")
+    @Size(min = 0, max = 100, message = "参数名称不能超过100个字符")
+    public String getConfigName()
+    {
+        return configName;
+    }
+
+    public void setConfigName(String configName)
+    {
+        this.configName = configName;
+    }
+
+    @NotBlank(message = "参数键名长度不能为空")
+    @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符")
+    public String getConfigKey()
+    {
+        return configKey;
+    }
+
+    public void setConfigKey(String configKey)
+    {
+        this.configKey = configKey;
+    }
+
+    @NotBlank(message = "参数键值不能为空")
+    @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符")
+    public String getConfigValue()
+    {
+        return configValue;
+    }
+
+    public void setConfigValue(String configValue)
+    {
+        this.configValue = configValue;
+    }
+
+    public String getConfigType()
+    {
+        return configType;
+    }
+
+    public void setConfigType(String configType)
+    {
+        this.configType = configType;
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("configId", getConfigId())
+            .append("configName", getConfigName())
+            .append("configKey", getConfigKey())
+            .append("configValue", getConfigValue())
+            .append("configType", getConfigType())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java
new file mode 100644
index 0000000..7fdea30
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java
@@ -0,0 +1,144 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 系统访问记录表 sys_logininfor
+ * 
+ * @author ruoyi
+ */
+public class SysLogininfor extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    @Excel(name = "序号", cellType = ColumnType.NUMERIC)
+    private Long infoId;
+
+    /** 用户账号 */
+    @Excel(name = "用户账号")
+    private String userName;
+
+    /** 登录状态 0成功 1失败 */
+    @Excel(name = "登录状态", readConverterExp = "0=成功,1=失败")
+    private String status;
+
+    /** 登录IP地址 */
+    @Excel(name = "登录地址")
+    private String ipaddr;
+
+    /** 登录地点 */
+    @Excel(name = "登录地点")
+    private String loginLocation;
+
+    /** 浏览器类型 */
+    @Excel(name = "浏览器")
+    private String browser;
+
+    /** 操作系统 */
+    @Excel(name = "操作系统")
+    private String os;
+
+    /** 提示消息 */
+    @Excel(name = "提示消息")
+    private String msg;
+
+    /** 访问时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date loginTime;
+
+    public Long getInfoId()
+    {
+        return infoId;
+    }
+
+    public void setInfoId(Long infoId)
+    {
+        this.infoId = infoId;
+    }
+
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public void setUserName(String userName)
+    {
+        this.userName = userName;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public String getIpaddr()
+    {
+        return ipaddr;
+    }
+
+    public void setIpaddr(String ipaddr)
+    {
+        this.ipaddr = ipaddr;
+    }
+
+    public String getLoginLocation()
+    {
+        return loginLocation;
+    }
+
+    public void setLoginLocation(String loginLocation)
+    {
+        this.loginLocation = loginLocation;
+    }
+
+    public String getBrowser()
+    {
+        return browser;
+    }
+
+    public void setBrowser(String browser)
+    {
+        this.browser = browser;
+    }
+
+    public String getOs()
+    {
+        return os;
+    }
+
+    public void setOs(String os)
+    {
+        this.os = os;
+    }
+
+    public String getMsg()
+    {
+        return msg;
+    }
+
+    public void setMsg(String msg)
+    {
+        this.msg = msg;
+    }
+
+    public Date getLoginTime()
+    {
+        return loginTime;
+    }
+
+    public void setLoginTime(Date loginTime)
+    {
+        this.loginTime = loginTime;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java
new file mode 100644
index 0000000..8603ad8
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java
@@ -0,0 +1,102 @@
+package com.ruoyi.system.domain;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.common.xss.Xss;
+
+/**
+ * 通知公告表 sys_notice
+ * 
+ * @author ruoyi
+ */
+public class SysNotice extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 公告ID */
+    private Long noticeId;
+
+    /** 公告标题 */
+    private String noticeTitle;
+
+    /** 公告类型(1通知 2公告) */
+    private String noticeType;
+
+    /** 公告内容 */
+    private String noticeContent;
+
+    /** 公告状态(0正常 1关闭) */
+    private String status;
+
+    public Long getNoticeId()
+    {
+        return noticeId;
+    }
+
+    public void setNoticeId(Long noticeId)
+    {
+        this.noticeId = noticeId;
+    }
+
+    public void setNoticeTitle(String noticeTitle)
+    {
+        this.noticeTitle = noticeTitle;
+    }
+
+    @Xss(message = "公告标题不能包含脚本字符")
+    @NotBlank(message = "公告标题不能为空")
+    @Size(min = 0, max = 50, message = "公告标题不能超过50个字符")
+    public String getNoticeTitle()
+    {
+        return noticeTitle;
+    }
+
+    public void setNoticeType(String noticeType)
+    {
+        this.noticeType = noticeType;
+    }
+
+    public String getNoticeType()
+    {
+        return noticeType;
+    }
+
+    public void setNoticeContent(String noticeContent)
+    {
+        this.noticeContent = noticeContent;
+    }
+
+    public String getNoticeContent()
+    {
+        return noticeContent;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("noticeId", getNoticeId())
+            .append("noticeTitle", getNoticeTitle())
+            .append("noticeType", getNoticeType())
+            .append("noticeContent", getNoticeContent())
+            .append("status", getStatus())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java
new file mode 100644
index 0000000..f6761df
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java
@@ -0,0 +1,269 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 操作日志记录表 oper_log
+ * 
+ * @author ruoyi
+ */
+public class SysOperLog extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 日志主键 */
+    @Excel(name = "操作序号", cellType = ColumnType.NUMERIC)
+    private Long operId;
+
+    /** 操作模块 */
+    @Excel(name = "操作模块")
+    private String title;
+
+    /** 业务类型(0其它 1新增 2修改 3删除) */
+    @Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据")
+    private Integer businessType;
+
+    /** 业务类型数组 */
+    private Integer[] businessTypes;
+
+    /** 请求方法 */
+    @Excel(name = "请求方法")
+    private String method;
+
+    /** 请求方式 */
+    @Excel(name = "请求方式")
+    private String requestMethod;
+
+    /** 操作类别(0其它 1后台用户 2手机端用户) */
+    @Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户")
+    private Integer operatorType;
+
+    /** 操作人员 */
+    @Excel(name = "操作人员")
+    private String operName;
+
+    /** 部门名称 */
+    @Excel(name = "部门名称")
+    private String deptName;
+
+    /** 请求url */
+    @Excel(name = "请求地址")
+    private String operUrl;
+
+    /** 操作地址 */
+    @Excel(name = "操作地址")
+    private String operIp;
+
+    /** 操作地点 */
+    @Excel(name = "操作地点")
+    private String operLocation;
+
+    /** 请求参数 */
+    @Excel(name = "请求参数")
+    private String operParam;
+
+    /** 返回参数 */
+    @Excel(name = "返回参数")
+    private String jsonResult;
+
+    /** 操作状态(0正常 1异常) */
+    @Excel(name = "状态", readConverterExp = "0=正常,1=异常")
+    private Integer status;
+
+    /** 错误消息 */
+    @Excel(name = "错误消息")
+    private String errorMsg;
+
+    /** 操作时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date operTime;
+
+    /** 消耗时间 */
+    @Excel(name = "消耗时间", suffix = "毫秒")
+    private Long costTime;
+
+    public Long getOperId()
+    {
+        return operId;
+    }
+
+    public void setOperId(Long operId)
+    {
+        this.operId = operId;
+    }
+
+    public String getTitle()
+    {
+        return title;
+    }
+
+    public void setTitle(String title)
+    {
+        this.title = title;
+    }
+
+    public Integer getBusinessType()
+    {
+        return businessType;
+    }
+
+    public void setBusinessType(Integer businessType)
+    {
+        this.businessType = businessType;
+    }
+
+    public Integer[] getBusinessTypes()
+    {
+        return businessTypes;
+    }
+
+    public void setBusinessTypes(Integer[] businessTypes)
+    {
+        this.businessTypes = businessTypes;
+    }
+
+    public String getMethod()
+    {
+        return method;
+    }
+
+    public void setMethod(String method)
+    {
+        this.method = method;
+    }
+
+    public String getRequestMethod()
+    {
+        return requestMethod;
+    }
+
+    public void setRequestMethod(String requestMethod)
+    {
+        this.requestMethod = requestMethod;
+    }
+
+    public Integer getOperatorType()
+    {
+        return operatorType;
+    }
+
+    public void setOperatorType(Integer operatorType)
+    {
+        this.operatorType = operatorType;
+    }
+
+    public String getOperName()
+    {
+        return operName;
+    }
+
+    public void setOperName(String operName)
+    {
+        this.operName = operName;
+    }
+
+    public String getDeptName()
+    {
+        return deptName;
+    }
+
+    public void setDeptName(String deptName)
+    {
+        this.deptName = deptName;
+    }
+
+    public String getOperUrl()
+    {
+        return operUrl;
+    }
+
+    public void setOperUrl(String operUrl)
+    {
+        this.operUrl = operUrl;
+    }
+
+    public String getOperIp()
+    {
+        return operIp;
+    }
+
+    public void setOperIp(String operIp)
+    {
+        this.operIp = operIp;
+    }
+
+    public String getOperLocation()
+    {
+        return operLocation;
+    }
+
+    public void setOperLocation(String operLocation)
+    {
+        this.operLocation = operLocation;
+    }
+
+    public String getOperParam()
+    {
+        return operParam;
+    }
+
+    public void setOperParam(String operParam)
+    {
+        this.operParam = operParam;
+    }
+
+    public String getJsonResult()
+    {
+        return jsonResult;
+    }
+
+    public void setJsonResult(String jsonResult)
+    {
+        this.jsonResult = jsonResult;
+    }
+
+    public Integer getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(Integer status)
+    {
+        this.status = status;
+    }
+
+    public String getErrorMsg()
+    {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg)
+    {
+        this.errorMsg = errorMsg;
+    }
+
+    public Date getOperTime()
+    {
+        return operTime;
+    }
+
+    public void setOperTime(Date operTime)
+    {
+        this.operTime = operTime;
+    }
+
+    public Long getCostTime()
+    {
+        return costTime;
+    }
+
+    public void setCostTime(Long costTime)
+    {
+        this.costTime = costTime;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java
new file mode 100644
index 0000000..62dbd47
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java
@@ -0,0 +1,124 @@
+package com.ruoyi.system.domain;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 岗位表 sys_post
+ * 
+ * @author ruoyi
+ */
+public class SysPost extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 岗位序号 */
+    @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC)
+    private Long postId;
+
+    /** 岗位编码 */
+    @Excel(name = "岗位编码")
+    private String postCode;
+
+    /** 岗位名称 */
+    @Excel(name = "岗位名称")
+    private String postName;
+
+    /** 岗位排序 */
+    @Excel(name = "岗位排序")
+    private Integer postSort;
+
+    /** 状态(0正常 1停用) */
+    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /** 用户是否存在此岗位标识 默认不存在 */
+    private boolean flag = false;
+
+    public Long getPostId()
+    {
+        return postId;
+    }
+
+    public void setPostId(Long postId)
+    {
+        this.postId = postId;
+    }
+
+    @NotBlank(message = "岗位编码不能为空")
+    @Size(min = 0, max = 64, message = "岗位编码长度不能超过64个字符")
+    public String getPostCode()
+    {
+        return postCode;
+    }
+
+    public void setPostCode(String postCode)
+    {
+        this.postCode = postCode;
+    }
+
+    @NotBlank(message = "岗位名称不能为空")
+    @Size(min = 0, max = 50, message = "岗位名称长度不能超过50个字符")
+    public String getPostName()
+    {
+        return postName;
+    }
+
+    public void setPostName(String postName)
+    {
+        this.postName = postName;
+    }
+
+    @NotNull(message = "显示顺序不能为空")
+    public Integer getPostSort()
+    {
+        return postSort;
+    }
+
+    public void setPostSort(Integer postSort)
+    {
+        this.postSort = postSort;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public boolean isFlag()
+    {
+        return flag;
+    }
+
+    public void setFlag(boolean flag)
+    {
+        this.flag = flag;
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("postId", getPostId())
+            .append("postCode", getPostCode())
+            .append("postName", getPostName())
+            .append("postSort", getPostSort())
+            .append("status", getStatus())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java
new file mode 100644
index 0000000..47b21bf
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java
@@ -0,0 +1,46 @@
+package com.ruoyi.system.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 角色和部门关联 sys_role_dept
+ * 
+ * @author ruoyi
+ */
+public class SysRoleDept
+{
+    /** 角色ID */
+    private Long roleId;
+    
+    /** 部门ID */
+    private Long deptId;
+
+    public Long getRoleId()
+    {
+        return roleId;
+    }
+
+    public void setRoleId(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    public Long getDeptId()
+    {
+        return deptId;
+    }
+
+    public void setDeptId(Long deptId)
+    {
+        this.deptId = deptId;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("roleId", getRoleId())
+            .append("deptId", getDeptId())
+            .toString();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java
new file mode 100644
index 0000000..de10a74
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java
@@ -0,0 +1,46 @@
+package com.ruoyi.system.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 角色和菜单关联 sys_role_menu
+ * 
+ * @author ruoyi
+ */
+public class SysRoleMenu
+{
+    /** 角色ID */
+    private Long roleId;
+    
+    /** 菜单ID */
+    private Long menuId;
+
+    public Long getRoleId()
+    {
+        return roleId;
+    }
+
+    public void setRoleId(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    public Long getMenuId()
+    {
+        return menuId;
+    }
+
+    public void setMenuId(Long menuId)
+    {
+        this.menuId = menuId;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("roleId", getRoleId())
+            .append("menuId", getMenuId())
+            .toString();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java
new file mode 100644
index 0000000..2bbd318
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java
@@ -0,0 +1,113 @@
+package com.ruoyi.system.domain;
+
+/**
+ * 当前在线会话
+ * 
+ * @author ruoyi
+ */
+public class SysUserOnline
+{
+    /** 会话编号 */
+    private String tokenId;
+
+    /** 部门名称 */
+    private String deptName;
+
+    /** 用户名称 */
+    private String userName;
+
+    /** 登录IP地址 */
+    private String ipaddr;
+
+    /** 登录地址 */
+    private String loginLocation;
+
+    /** 浏览器类型 */
+    private String browser;
+
+    /** 操作系统 */
+    private String os;
+
+    /** 登录时间 */
+    private Long loginTime;
+
+    public String getTokenId()
+    {
+        return tokenId;
+    }
+
+    public void setTokenId(String tokenId)
+    {
+        this.tokenId = tokenId;
+    }
+
+    public String getDeptName()
+    {
+        return deptName;
+    }
+
+    public void setDeptName(String deptName)
+    {
+        this.deptName = deptName;
+    }
+
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public void setUserName(String userName)
+    {
+        this.userName = userName;
+    }
+
+    public String getIpaddr()
+    {
+        return ipaddr;
+    }
+
+    public void setIpaddr(String ipaddr)
+    {
+        this.ipaddr = ipaddr;
+    }
+
+    public String getLoginLocation()
+    {
+        return loginLocation;
+    }
+
+    public void setLoginLocation(String loginLocation)
+    {
+        this.loginLocation = loginLocation;
+    }
+
+    public String getBrowser()
+    {
+        return browser;
+    }
+
+    public void setBrowser(String browser)
+    {
+        this.browser = browser;
+    }
+
+    public String getOs()
+    {
+        return os;
+    }
+
+    public void setOs(String os)
+    {
+        this.os = os;
+    }
+
+    public Long getLoginTime()
+    {
+        return loginTime;
+    }
+
+    public void setLoginTime(Long loginTime)
+    {
+        this.loginTime = loginTime;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java
new file mode 100644
index 0000000..6e8c416
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java
@@ -0,0 +1,46 @@
+package com.ruoyi.system.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 用户和岗位关联 sys_user_post
+ * 
+ * @author ruoyi
+ */
+public class SysUserPost
+{
+    /** 用户ID */
+    private Long userId;
+    
+    /** 岗位ID */
+    private Long postId;
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public Long getPostId()
+    {
+        return postId;
+    }
+
+    public void setPostId(Long postId)
+    {
+        this.postId = postId;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("userId", getUserId())
+            .append("postId", getPostId())
+            .toString();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java
new file mode 100644
index 0000000..4d15810
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java
@@ -0,0 +1,46 @@
+package com.ruoyi.system.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 用户和角色关联 sys_user_role
+ * 
+ * @author ruoyi
+ */
+public class SysUserRole
+{
+    /** 用户ID */
+    private Long userId;
+    
+    /** 角色ID */
+    private Long roleId;
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public Long getRoleId()
+    {
+        return roleId;
+    }
+
+    public void setRoleId(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("userId", getUserId())
+            .append("roleId", getRoleId())
+            .toString();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java
new file mode 100644
index 0000000..a5d5fdc
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java
@@ -0,0 +1,106 @@
+package com.ruoyi.system.domain.vo;
+
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 路由显示信息
+ * 
+ * @author ruoyi
+ */
+public class MetaVo
+{
+    /**
+     * 设置该路由在侧边栏和面包屑中展示的名字
+     */
+    private String title;
+
+    /**
+     * 设置该路由的图标,对应路径src/assets/icons/svg
+     */
+    private String icon;
+
+    /**
+     * 设置为true,则不会被 <keep-alive>缓存
+     */
+    private boolean noCache;
+
+    /**
+     * 内链地址(http(s)://开头)
+     */
+    private String link;
+
+    public MetaVo()
+    {
+    }
+
+    public MetaVo(String title, String icon)
+    {
+        this.title = title;
+        this.icon = icon;
+    }
+
+    public MetaVo(String title, String icon, boolean noCache)
+    {
+        this.title = title;
+        this.icon = icon;
+        this.noCache = noCache;
+    }
+
+    public MetaVo(String title, String icon, String link)
+    {
+        this.title = title;
+        this.icon = icon;
+        this.link = link;
+    }
+
+    public MetaVo(String title, String icon, boolean noCache, String link)
+    {
+        this.title = title;
+        this.icon = icon;
+        this.noCache = noCache;
+        if (StringUtils.ishttp(link))
+        {
+            this.link = link;
+        }
+    }
+
+    public boolean isNoCache()
+    {
+        return noCache;
+    }
+
+    public void setNoCache(boolean noCache)
+    {
+        this.noCache = noCache;
+    }
+
+    public String getTitle()
+    {
+        return title;
+    }
+
+    public void setTitle(String title)
+    {
+        this.title = title;
+    }
+
+    public String getIcon()
+    {
+        return icon;
+    }
+
+    public void setIcon(String icon)
+    {
+        this.icon = icon;
+    }
+
+    public String getLink()
+    {
+        return link;
+    }
+
+    public void setLink(String link)
+    {
+        this.link = link;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java
new file mode 100644
index 0000000..afff8c9
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java
@@ -0,0 +1,148 @@
+package com.ruoyi.system.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.List;
+
+/**
+ * 路由配置信息
+ * 
+ * @author ruoyi
+ */
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class RouterVo
+{
+    /**
+     * 路由名字
+     */
+    private String name;
+
+    /**
+     * 路由地址
+     */
+    private String path;
+
+    /**
+     * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现
+     */
+    private boolean hidden;
+
+    /**
+     * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
+     */
+    private String redirect;
+
+    /**
+     * 组件地址
+     */
+    private String component;
+
+    /**
+     * 路由参数:如 {"id": 1, "name": "ry"}
+     */
+    private String query;
+
+    /**
+     * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
+     */
+    private Boolean alwaysShow;
+
+    /**
+     * 其他元素
+     */
+    private MetaVo meta;
+
+    /**
+     * 子路由
+     */
+    private List<RouterVo> children;
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean getHidden()
+    {
+        return hidden;
+    }
+
+    public void setHidden(boolean hidden)
+    {
+        this.hidden = hidden;
+    }
+
+    public String getRedirect()
+    {
+        return redirect;
+    }
+
+    public void setRedirect(String redirect)
+    {
+        this.redirect = redirect;
+    }
+
+    public String getComponent()
+    {
+        return component;
+    }
+
+    public void setComponent(String component)
+    {
+        this.component = component;
+    }
+
+    public String getQuery()
+    {
+        return query;
+    }
+
+    public void setQuery(String query)
+    {
+        this.query = query;
+    }
+
+    public Boolean getAlwaysShow()
+    {
+        return alwaysShow;
+    }
+
+    public void setAlwaysShow(Boolean alwaysShow)
+    {
+        this.alwaysShow = alwaysShow;
+    }
+
+    public MetaVo getMeta()
+    {
+        return meta;
+    }
+
+    public void setMeta(MetaVo meta)
+    {
+        this.meta = meta;
+    }
+
+    public List<RouterVo> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<RouterVo> children)
+    {
+        this.children = children;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java
new file mode 100644
index 0000000..13d49d6
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java
@@ -0,0 +1,76 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysConfig;
+
+/**
+ * 参数配置 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysConfigMapper
+{
+    /**
+     * 查询参数配置信息
+     * 
+     * @param config 参数配置信息
+     * @return 参数配置信息
+     */
+    public SysConfig selectConfig(SysConfig config);
+
+    /**
+     * 通过ID查询配置
+     * 
+     * @param configId 参数ID
+     * @return 参数配置信息
+     */
+    public SysConfig selectConfigById(Long configId);
+
+    /**
+     * 查询参数配置列表
+     * 
+     * @param config 参数配置信息
+     * @return 参数配置集合
+     */
+    public List<SysConfig> selectConfigList(SysConfig config);
+
+    /**
+     * 根据键名查询参数配置信息
+     * 
+     * @param configKey 参数键名
+     * @return 参数配置信息
+     */
+    public SysConfig checkConfigKeyUnique(String configKey);
+
+    /**
+     * 新增参数配置
+     * 
+     * @param config 参数配置信息
+     * @return 结果
+     */
+    public int insertConfig(SysConfig config);
+
+    /**
+     * 修改参数配置
+     * 
+     * @param config 参数配置信息
+     * @return 结果
+     */
+    public int updateConfig(SysConfig config);
+
+    /**
+     * 删除参数配置
+     * 
+     * @param configId 参数ID
+     * @return 结果
+     */
+    public int deleteConfigById(Long configId);
+
+    /**
+     * 批量删除参数信息
+     * 
+     * @param configIds 需要删除的参数ID
+     * @return 结果
+     */
+    public int deleteConfigByIds(Long[] configIds);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java
new file mode 100644
index 0000000..384a9b6
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java
@@ -0,0 +1,118 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.common.core.domain.entity.SysDept;
+
+/**
+ * 部门管理 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysDeptMapper
+{
+    /**
+     * 查询部门管理数据
+     * 
+     * @param dept 部门信息
+     * @return 部门信息集合
+     */
+    public List<SysDept> selectDeptList(SysDept dept);
+
+    /**
+     * 根据角色ID查询部门树信息
+     * 
+     * @param roleId 角色ID
+     * @param deptCheckStrictly 部门树选择项是否关联显示
+     * @return 选中部门列表
+     */
+    public List<Long> selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly);
+
+    /**
+     * 根据部门ID查询信息
+     * 
+     * @param deptId 部门ID
+     * @return 部门信息
+     */
+    public SysDept selectDeptById(Long deptId);
+
+    /**
+     * 根据ID查询所有子部门
+     * 
+     * @param deptId 部门ID
+     * @return 部门列表
+     */
+    public List<SysDept> selectChildrenDeptById(Long deptId);
+
+    /**
+     * 根据ID查询所有子部门(正常状态)
+     * 
+     * @param deptId 部门ID
+     * @return 子部门数
+     */
+    public int selectNormalChildrenDeptById(Long deptId);
+
+    /**
+     * 是否存在子节点
+     * 
+     * @param deptId 部门ID
+     * @return 结果
+     */
+    public int hasChildByDeptId(Long deptId);
+
+    /**
+     * 查询部门是否存在用户
+     * 
+     * @param deptId 部门ID
+     * @return 结果
+     */
+    public int checkDeptExistUser(Long deptId);
+
+    /**
+     * 校验部门名称是否唯一
+     * 
+     * @param deptName 部门名称
+     * @param parentId 父部门ID
+     * @return 结果
+     */
+    public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId);
+
+    /**
+     * 新增部门信息
+     * 
+     * @param dept 部门信息
+     * @return 结果
+     */
+    public int insertDept(SysDept dept);
+
+    /**
+     * 修改部门信息
+     * 
+     * @param dept 部门信息
+     * @return 结果
+     */
+    public int updateDept(SysDept dept);
+
+    /**
+     * 修改所在部门正常状态
+     * 
+     * @param deptIds 部门ID组
+     */
+    public void updateDeptStatusNormal(Long[] deptIds);
+
+    /**
+     * 修改子元素关系
+     * 
+     * @param depts 子元素
+     * @return 结果
+     */
+    public int updateDeptChildren(@Param("depts") List<SysDept> depts);
+
+    /**
+     * 删除部门管理信息
+     * 
+     * @param deptId 部门ID
+     * @return 结果
+     */
+    public int deleteDeptById(Long deptId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java
new file mode 100644
index 0000000..a341f1e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java
@@ -0,0 +1,95 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+
+/**
+ * 字典表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysDictDataMapper
+{
+    /**
+     * 根据条件分页查询字典数据
+     * 
+     * @param dictData 字典数据信息
+     * @return 字典数据集合信息
+     */
+    public List<SysDictData> selectDictDataList(SysDictData dictData);
+
+    /**
+     * 根据字典类型查询字典数据
+     * 
+     * @param dictType 字典类型
+     * @return 字典数据集合信息
+     */
+    public List<SysDictData> selectDictDataByType(String dictType);
+
+    /**
+     * 根据字典类型和字典键值查询字典数据信息
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典键值
+     * @return 字典标签
+     */
+    public String selectDictLabel(@Param("dictType") String dictType, @Param("dictValue") String dictValue);
+
+    /**
+     * 根据字典数据ID查询信息
+     * 
+     * @param dictCode 字典数据ID
+     * @return 字典数据
+     */
+    public SysDictData selectDictDataById(Long dictCode);
+
+    /**
+     * 查询字典数据
+     * 
+     * @param dictType 字典类型
+     * @return 字典数据
+     */
+    public int countDictDataByType(String dictType);
+
+    /**
+     * 通过字典ID删除字典数据信息
+     * 
+     * @param dictCode 字典数据ID
+     * @return 结果
+     */
+    public int deleteDictDataById(Long dictCode);
+
+    /**
+     * 批量删除字典数据信息
+     * 
+     * @param dictCodes 需要删除的字典数据ID
+     * @return 结果
+     */
+    public int deleteDictDataByIds(Long[] dictCodes);
+
+    /**
+     * 新增字典数据信息
+     * 
+     * @param dictData 字典数据信息
+     * @return 结果
+     */
+    public int insertDictData(SysDictData dictData);
+
+    /**
+     * 修改字典数据信息
+     * 
+     * @param dictData 字典数据信息
+     * @return 结果
+     */
+    public int updateDictData(SysDictData dictData);
+
+    /**
+     * 同步修改字典类型
+     * 
+     * @param oldDictType 旧字典类型
+     * @param newDictType 新旧字典类型
+     * @return 结果
+     */
+    public int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java
new file mode 100644
index 0000000..5fb48fb
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java
@@ -0,0 +1,83 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.common.core.domain.entity.SysDictType;
+
+/**
+ * 字典表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysDictTypeMapper
+{
+    /**
+     * 根据条件分页查询字典类型
+     * 
+     * @param dictType 字典类型信息
+     * @return 字典类型集合信息
+     */
+    public List<SysDictType> selectDictTypeList(SysDictType dictType);
+
+    /**
+     * 根据所有字典类型
+     * 
+     * @return 字典类型集合信息
+     */
+    public List<SysDictType> selectDictTypeAll();
+
+    /**
+     * 根据字典类型ID查询信息
+     * 
+     * @param dictId 字典类型ID
+     * @return 字典类型
+     */
+    public SysDictType selectDictTypeById(Long dictId);
+
+    /**
+     * 根据字典类型查询信息
+     * 
+     * @param dictType 字典类型
+     * @return 字典类型
+     */
+    public SysDictType selectDictTypeByType(String dictType);
+
+    /**
+     * 通过字典ID删除字典信息
+     * 
+     * @param dictId 字典ID
+     * @return 结果
+     */
+    public int deleteDictTypeById(Long dictId);
+
+    /**
+     * 批量删除字典类型信息
+     * 
+     * @param dictIds 需要删除的字典ID
+     * @return 结果
+     */
+    public int deleteDictTypeByIds(Long[] dictIds);
+
+    /**
+     * 新增字典类型信息
+     * 
+     * @param dictType 字典类型信息
+     * @return 结果
+     */
+    public int insertDictType(SysDictType dictType);
+
+    /**
+     * 修改字典类型信息
+     * 
+     * @param dictType 字典类型信息
+     * @return 结果
+     */
+    public int updateDictType(SysDictType dictType);
+
+    /**
+     * 校验字典类型称是否唯一
+     * 
+     * @param dictType 字典类型
+     * @return 结果
+     */
+    public SysDictType checkDictTypeUnique(String dictType);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java
new file mode 100644
index 0000000..629866f
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java
@@ -0,0 +1,42 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysLogininfor;
+
+/**
+ * 系统访问日志情况信息 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysLogininforMapper
+{
+    /**
+     * 新增系统登录日志
+     * 
+     * @param logininfor 访问日志对象
+     */
+    public void insertLogininfor(SysLogininfor logininfor);
+
+    /**
+     * 查询系统登录日志集合
+     * 
+     * @param logininfor 访问日志对象
+     * @return 登录记录集合
+     */
+    public List<SysLogininfor> selectLogininforList(SysLogininfor logininfor);
+
+    /**
+     * 批量删除系统登录日志
+     * 
+     * @param infoIds 需要删除的登录日志ID
+     * @return 结果
+     */
+    public int deleteLogininforByIds(Long[] infoIds);
+
+    /**
+     * 清空系统登录日志
+     * 
+     * @return 结果
+     */
+    public int cleanLogininfor();
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java
new file mode 100644
index 0000000..99c0c50
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java
@@ -0,0 +1,125 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+
+/**
+ * 菜单表 数据层
+ *
+ * @author ruoyi
+ */
+public interface SysMenuMapper
+{
+    /**
+     * 查询系统菜单列表
+     *
+     * @param menu 菜单信息
+     * @return 菜单列表
+     */
+    public List<SysMenu> selectMenuList(SysMenu menu);
+
+    /**
+     * 根据用户所有权限
+     *
+     * @return 权限列表
+     */
+    public List<String> selectMenuPerms();
+
+    /**
+     * 根据用户查询系统菜单列表
+     *
+     * @param menu 菜单信息
+     * @return 菜单列表
+     */
+    public List<SysMenu> selectMenuListByUserId(SysMenu menu);
+
+    /**
+     * 根据角色ID查询权限
+     * 
+     * @param roleId 角色ID
+     * @return 权限列表
+     */
+    public List<String> selectMenuPermsByRoleId(Long roleId);
+
+    /**
+     * 根据用户ID查询权限
+     *
+     * @param userId 用户ID
+     * @return 权限列表
+     */
+    public List<String> selectMenuPermsByUserId(Long userId);
+
+    /**
+     * 根据用户ID查询菜单
+     *
+     * @return 菜单列表
+     */
+    public List<SysMenu> selectMenuTreeAll();
+
+    /**
+     * 根据用户ID查询菜单
+     *
+     * @param userId 用户ID
+     * @return 菜单列表
+     */
+    public List<SysMenu> selectMenuTreeByUserId(Long userId);
+
+    /**
+     * 根据角色ID查询菜单树信息
+     * 
+     * @param roleId 角色ID
+     * @param menuCheckStrictly 菜单树选择项是否关联显示
+     * @return 选中菜单列表
+     */
+    public List<Long> selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly);
+
+    /**
+     * 根据菜单ID查询信息
+     *
+     * @param menuId 菜单ID
+     * @return 菜单信息
+     */
+    public SysMenu selectMenuById(Long menuId);
+
+    /**
+     * 是否存在菜单子节点
+     *
+     * @param menuId 菜单ID
+     * @return 结果
+     */
+    public int hasChildByMenuId(Long menuId);
+
+    /**
+     * 新增菜单信息
+     *
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    public int insertMenu(SysMenu menu);
+
+    /**
+     * 修改菜单信息
+     *
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    public int updateMenu(SysMenu menu);
+
+    /**
+     * 删除菜单管理信息
+     *
+     * @param menuId 菜单ID
+     * @return 结果
+     */
+    public int deleteMenuById(Long menuId);
+
+    /**
+     * 校验菜单名称是否唯一
+     *
+     * @param menuName 菜单名称
+     * @param parentId 父菜单ID
+     * @return 结果
+     */
+    public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java
new file mode 100644
index 0000000..c34f0a2
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java
@@ -0,0 +1,60 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysNotice;
+
+/**
+ * 通知公告表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysNoticeMapper
+{
+    /**
+     * 查询公告信息
+     * 
+     * @param noticeId 公告ID
+     * @return 公告信息
+     */
+    public SysNotice selectNoticeById(Long noticeId);
+
+    /**
+     * 查询公告列表
+     * 
+     * @param notice 公告信息
+     * @return 公告集合
+     */
+    public List<SysNotice> selectNoticeList(SysNotice notice);
+
+    /**
+     * 新增公告
+     * 
+     * @param notice 公告信息
+     * @return 结果
+     */
+    public int insertNotice(SysNotice notice);
+
+    /**
+     * 修改公告
+     * 
+     * @param notice 公告信息
+     * @return 结果
+     */
+    public int updateNotice(SysNotice notice);
+
+    /**
+     * 批量删除公告
+     * 
+     * @param noticeId 公告ID
+     * @return 结果
+     */
+    public int deleteNoticeById(Long noticeId);
+
+    /**
+     * 批量删除公告信息
+     * 
+     * @param noticeIds 需要删除的公告ID
+     * @return 结果
+     */
+    public int deleteNoticeByIds(Long[] noticeIds);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java
new file mode 100644
index 0000000..2ae6457
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java
@@ -0,0 +1,48 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysOperLog;
+
+/**
+ * 操作日志 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysOperLogMapper
+{
+    /**
+     * 新增操作日志
+     * 
+     * @param operLog 操作日志对象
+     */
+    public void insertOperlog(SysOperLog operLog);
+
+    /**
+     * 查询系统操作日志集合
+     * 
+     * @param operLog 操作日志对象
+     * @return 操作日志集合
+     */
+    public List<SysOperLog> selectOperLogList(SysOperLog operLog);
+
+    /**
+     * 批量删除系统操作日志
+     * 
+     * @param operIds 需要删除的操作日志ID
+     * @return 结果
+     */
+    public int deleteOperLogByIds(Long[] operIds);
+
+    /**
+     * 查询操作日志详细
+     * 
+     * @param operId 操作ID
+     * @return 操作日志对象
+     */
+    public SysOperLog selectOperLogById(Long operId);
+
+    /**
+     * 清空操作日志
+     */
+    public void cleanOperLog();
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java
new file mode 100644
index 0000000..19be227
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java
@@ -0,0 +1,99 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysPost;
+
+/**
+ * 岗位信息 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysPostMapper
+{
+    /**
+     * 查询岗位数据集合
+     * 
+     * @param post 岗位信息
+     * @return 岗位数据集合
+     */
+    public List<SysPost> selectPostList(SysPost post);
+
+    /**
+     * 查询所有岗位
+     * 
+     * @return 岗位列表
+     */
+    public List<SysPost> selectPostAll();
+
+    /**
+     * 通过岗位ID查询岗位信息
+     * 
+     * @param postId 岗位ID
+     * @return 角色对象信息
+     */
+    public SysPost selectPostById(Long postId);
+
+    /**
+     * 根据用户ID获取岗位选择框列表
+     * 
+     * @param userId 用户ID
+     * @return 选中岗位ID列表
+     */
+    public List<Long> selectPostListByUserId(Long userId);
+
+    /**
+     * 查询用户所属岗位组
+     * 
+     * @param userName 用户名
+     * @return 结果
+     */
+    public List<SysPost> selectPostsByUserName(String userName);
+
+    /**
+     * 删除岗位信息
+     * 
+     * @param postId 岗位ID
+     * @return 结果
+     */
+    public int deletePostById(Long postId);
+
+    /**
+     * 批量删除岗位信息
+     * 
+     * @param postIds 需要删除的岗位ID
+     * @return 结果
+     */
+    public int deletePostByIds(Long[] postIds);
+
+    /**
+     * 修改岗位信息
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    public int updatePost(SysPost post);
+
+    /**
+     * 新增岗位信息
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    public int insertPost(SysPost post);
+
+    /**
+     * 校验岗位名称
+     * 
+     * @param postName 岗位名称
+     * @return 结果
+     */
+    public SysPost checkPostNameUnique(String postName);
+
+    /**
+     * 校验岗位编码
+     * 
+     * @param postCode 岗位编码
+     * @return 结果
+     */
+    public SysPost checkPostCodeUnique(String postCode);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java
new file mode 100644
index 0000000..f9d3a2f
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java
@@ -0,0 +1,44 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysRoleDept;
+
+/**
+ * 角色与部门关联表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysRoleDeptMapper
+{
+    /**
+     * 通过角色ID删除角色和部门关联
+     * 
+     * @param roleId 角色ID
+     * @return 结果
+     */
+    public int deleteRoleDeptByRoleId(Long roleId);
+
+    /**
+     * 批量删除角色部门关联信息
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteRoleDept(Long[] ids);
+
+    /**
+     * 查询部门使用数量
+     * 
+     * @param deptId 部门ID
+     * @return 结果
+     */
+    public int selectCountRoleDeptByDeptId(Long deptId);
+
+    /**
+     * 批量新增角色部门信息
+     * 
+     * @param roleDeptList 角色部门列表
+     * @return 结果
+     */
+    public int batchRoleDept(List<SysRoleDept> roleDeptList);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java
new file mode 100644
index 0000000..cf2bd8c
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java
@@ -0,0 +1,107 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.common.core.domain.entity.SysRole;
+
+/**
+ * 角色表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysRoleMapper
+{
+    /**
+     * 根据条件分页查询角色数据
+     * 
+     * @param role 角色信息
+     * @return 角色数据集合信息
+     */
+    public List<SysRole> selectRoleList(SysRole role);
+
+    /**
+     * 根据用户ID查询角色
+     * 
+     * @param userId 用户ID
+     * @return 角色列表
+     */
+    public List<SysRole> selectRolePermissionByUserId(Long userId);
+
+    /**
+     * 查询所有角色
+     * 
+     * @return 角色列表
+     */
+    public List<SysRole> selectRoleAll();
+
+    /**
+     * 根据用户ID获取角色选择框列表
+     * 
+     * @param userId 用户ID
+     * @return 选中角色ID列表
+     */
+    public List<Long> selectRoleListByUserId(Long userId);
+
+    /**
+     * 通过角色ID查询角色
+     * 
+     * @param roleId 角色ID
+     * @return 角色对象信息
+     */
+    public SysRole selectRoleById(Long roleId);
+
+    /**
+     * 根据用户ID查询角色
+     * 
+     * @param userName 用户名
+     * @return 角色列表
+     */
+    public List<SysRole> selectRolesByUserName(String userName);
+
+    /**
+     * 校验角色名称是否唯一
+     * 
+     * @param roleName 角色名称
+     * @return 角色信息
+     */
+    public SysRole checkRoleNameUnique(String roleName);
+
+    /**
+     * 校验角色权限是否唯一
+     * 
+     * @param roleKey 角色权限
+     * @return 角色信息
+     */
+    public SysRole checkRoleKeyUnique(String roleKey);
+
+    /**
+     * 修改角色信息
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    public int updateRole(SysRole role);
+
+    /**
+     * 新增角色信息
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    public int insertRole(SysRole role);
+
+    /**
+     * 通过角色ID删除角色
+     * 
+     * @param roleId 角色ID
+     * @return 结果
+     */
+    public int deleteRoleById(Long roleId);
+
+    /**
+     * 批量删除角色信息
+     * 
+     * @param roleIds 需要删除的角色ID
+     * @return 结果
+     */
+    public int deleteRoleByIds(Long[] roleIds);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java
new file mode 100644
index 0000000..6602bee
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java
@@ -0,0 +1,44 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysRoleMenu;
+
+/**
+ * 角色与菜单关联表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysRoleMenuMapper
+{
+    /**
+     * 查询菜单使用数量
+     * 
+     * @param menuId 菜单ID
+     * @return 结果
+     */
+    public int checkMenuExistRole(Long menuId);
+
+    /**
+     * 通过角色ID删除角色和菜单关联
+     * 
+     * @param roleId 角色ID
+     * @return 结果
+     */
+    public int deleteRoleMenuByRoleId(Long roleId);
+
+    /**
+     * 批量删除角色菜单关联信息
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteRoleMenu(Long[] ids);
+
+    /**
+     * 批量新增角色菜单信息
+     * 
+     * @param roleMenuList 角色菜单列表
+     * @return 结果
+     */
+    public int batchRoleMenu(List<SysRoleMenu> roleMenuList);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
new file mode 100644
index 0000000..76e1c79
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
@@ -0,0 +1,127 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.common.core.domain.entity.SysUser;
+
+/**
+ * 用户表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysUserMapper
+{
+    /**
+     * 根据条件分页查询用户列表
+     * 
+     * @param sysUser 用户信息
+     * @return 用户信息集合信息
+     */
+    public List<SysUser> selectUserList(SysUser sysUser);
+
+    /**
+     * 根据条件分页查询已配用户角色列表
+     * 
+     * @param user 用户信息
+     * @return 用户信息集合信息
+     */
+    public List<SysUser> selectAllocatedList(SysUser user);
+
+    /**
+     * 根据条件分页查询未分配用户角色列表
+     * 
+     * @param user 用户信息
+     * @return 用户信息集合信息
+     */
+    public List<SysUser> selectUnallocatedList(SysUser user);
+
+    /**
+     * 通过用户名查询用户
+     * 
+     * @param userName 用户名
+     * @return 用户对象信息
+     */
+    public SysUser selectUserByUserName(String userName);
+
+    /**
+     * 通过用户ID查询用户
+     * 
+     * @param userId 用户ID
+     * @return 用户对象信息
+     */
+    public SysUser selectUserById(Long userId);
+
+    /**
+     * 新增用户信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public int insertUser(SysUser user);
+
+    /**
+     * 修改用户信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public int updateUser(SysUser user);
+
+    /**
+     * 修改用户头像
+     * 
+     * @param userName 用户名
+     * @param avatar 头像地址
+     * @return 结果
+     */
+    public int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar);
+
+    /**
+     * 重置用户密码
+     * 
+     * @param userName 用户名
+     * @param password 密码
+     * @return 结果
+     */
+    public int resetUserPwd(@Param("userName") String userName, @Param("password") String password);
+
+    /**
+     * 通过用户ID删除用户
+     * 
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public int deleteUserById(Long userId);
+
+    /**
+     * 批量删除用户信息
+     * 
+     * @param userIds 需要删除的用户ID
+     * @return 结果
+     */
+    public int deleteUserByIds(Long[] userIds);
+
+    /**
+     * 校验用户名称是否唯一
+     * 
+     * @param userName 用户名称
+     * @return 结果
+     */
+    public SysUser checkUserNameUnique(String userName);
+
+    /**
+     * 校验手机号码是否唯一
+     *
+     * @param phonenumber 手机号码
+     * @return 结果
+     */
+    public SysUser checkPhoneUnique(String phonenumber);
+
+    /**
+     * 校验email是否唯一
+     *
+     * @param email 用户邮箱
+     * @return 结果
+     */
+    public SysUser checkEmailUnique(String email);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java
new file mode 100644
index 0000000..2a6a720
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java
@@ -0,0 +1,44 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysUserPost;
+
+/**
+ * 用户与岗位关联表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysUserPostMapper
+{
+    /**
+     * 通过用户ID删除用户和岗位关联
+     * 
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public int deleteUserPostByUserId(Long userId);
+
+    /**
+     * 通过岗位ID查询岗位使用数量
+     * 
+     * @param postId 岗位ID
+     * @return 结果
+     */
+    public int countUserPostById(Long postId);
+
+    /**
+     * 批量删除用户和岗位关联
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteUserPost(Long[] ids);
+
+    /**
+     * 批量新增用户岗位信息
+     * 
+     * @param userPostList 用户岗位列表
+     * @return 结果
+     */
+    public int batchUserPost(List<SysUserPost> userPostList);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java
new file mode 100644
index 0000000..3143ec8
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java
@@ -0,0 +1,62 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.system.domain.SysUserRole;
+
+/**
+ * 用户与角色关联表 数据层
+ * 
+ * @author ruoyi
+ */
+public interface SysUserRoleMapper
+{
+    /**
+     * 通过用户ID删除用户和角色关联
+     * 
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public int deleteUserRoleByUserId(Long userId);
+
+    /**
+     * 批量删除用户和角色关联
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteUserRole(Long[] ids);
+
+    /**
+     * 通过角色ID查询角色使用数量
+     * 
+     * @param roleId 角色ID
+     * @return 结果
+     */
+    public int countUserRoleByRoleId(Long roleId);
+
+    /**
+     * 批量新增用户角色信息
+     * 
+     * @param userRoleList 用户角色列表
+     * @return 结果
+     */
+    public int batchUserRole(List<SysUserRole> userRoleList);
+
+    /**
+     * 删除用户和角色关联信息
+     * 
+     * @param userRole 用户和角色关联信息
+     * @return 结果
+     */
+    public int deleteUserRoleInfo(SysUserRole userRole);
+
+    /**
+     * 批量取消授权用户角色
+     * 
+     * @param roleId 角色ID
+     * @param userIds 需要删除的用户数据ID
+     * @return 结果
+     */
+    public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java
new file mode 100644
index 0000000..b307776
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java
@@ -0,0 +1,89 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysConfig;
+
+/**
+ * 参数配置 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysConfigService
+{
+    /**
+     * 查询参数配置信息
+     * 
+     * @param configId 参数配置ID
+     * @return 参数配置信息
+     */
+    public SysConfig selectConfigById(Long configId);
+
+    /**
+     * 根据键名查询参数配置信息
+     * 
+     * @param configKey 参数键名
+     * @return 参数键值
+     */
+    public String selectConfigByKey(String configKey);
+
+    /**
+     * 获取验证码开关
+     * 
+     * @return true开启,false关闭
+     */
+    public boolean selectCaptchaEnabled();
+
+    /**
+     * 查询参数配置列表
+     * 
+     * @param config 参数配置信息
+     * @return 参数配置集合
+     */
+    public List<SysConfig> selectConfigList(SysConfig config);
+
+    /**
+     * 新增参数配置
+     * 
+     * @param config 参数配置信息
+     * @return 结果
+     */
+    public int insertConfig(SysConfig config);
+
+    /**
+     * 修改参数配置
+     * 
+     * @param config 参数配置信息
+     * @return 结果
+     */
+    public int updateConfig(SysConfig config);
+
+    /**
+     * 批量删除参数信息
+     * 
+     * @param configIds 需要删除的参数ID
+     */
+    public void deleteConfigByIds(Long[] configIds);
+
+    /**
+     * 加载参数缓存数据
+     */
+    public void loadingConfigCache();
+
+    /**
+     * 清空参数缓存数据
+     */
+    public void clearConfigCache();
+
+    /**
+     * 重置参数缓存数据
+     */
+    public void resetConfigCache();
+
+    /**
+     * 校验参数键名是否唯一
+     * 
+     * @param config 参数信息
+     * @return 结果
+     */
+    public boolean checkConfigKeyUnique(SysConfig config);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java
new file mode 100644
index 0000000..f228208
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java
@@ -0,0 +1,124 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.common.core.domain.TreeSelect;
+import com.ruoyi.common.core.domain.entity.SysDept;
+
+/**
+ * 部门管理 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysDeptService
+{
+    /**
+     * 查询部门管理数据
+     * 
+     * @param dept 部门信息
+     * @return 部门信息集合
+     */
+    public List<SysDept> selectDeptList(SysDept dept);
+
+    /**
+     * 查询部门树结构信息
+     * 
+     * @param dept 部门信息
+     * @return 部门树信息集合
+     */
+    public List<TreeSelect> selectDeptTreeList(SysDept dept);
+
+    /**
+     * 构建前端所需要树结构
+     * 
+     * @param depts 部门列表
+     * @return 树结构列表
+     */
+    public List<SysDept> buildDeptTree(List<SysDept> depts);
+
+    /**
+     * 构建前端所需要下拉树结构
+     * 
+     * @param depts 部门列表
+     * @return 下拉树结构列表
+     */
+    public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts);
+
+    /**
+     * 根据角色ID查询部门树信息
+     * 
+     * @param roleId 角色ID
+     * @return 选中部门列表
+     */
+    public List<Long> selectDeptListByRoleId(Long roleId);
+
+    /**
+     * 根据部门ID查询信息
+     * 
+     * @param deptId 部门ID
+     * @return 部门信息
+     */
+    public SysDept selectDeptById(Long deptId);
+
+    /**
+     * 根据ID查询所有子部门(正常状态)
+     * 
+     * @param deptId 部门ID
+     * @return 子部门数
+     */
+    public int selectNormalChildrenDeptById(Long deptId);
+
+    /**
+     * 是否存在部门子节点
+     * 
+     * @param deptId 部门ID
+     * @return 结果
+     */
+    public boolean hasChildByDeptId(Long deptId);
+
+    /**
+     * 查询部门是否存在用户
+     * 
+     * @param deptId 部门ID
+     * @return 结果 true 存在 false 不存在
+     */
+    public boolean checkDeptExistUser(Long deptId);
+
+    /**
+     * 校验部门名称是否唯一
+     * 
+     * @param dept 部门信息
+     * @return 结果
+     */
+    public boolean checkDeptNameUnique(SysDept dept);
+
+    /**
+     * 校验部门是否有数据权限
+     * 
+     * @param deptId 部门id
+     */
+    public void checkDeptDataScope(Long deptId);
+
+    /**
+     * 新增保存部门信息
+     * 
+     * @param dept 部门信息
+     * @return 结果
+     */
+    public int insertDept(SysDept dept);
+
+    /**
+     * 修改保存部门信息
+     * 
+     * @param dept 部门信息
+     * @return 结果
+     */
+    public int updateDept(SysDept dept);
+
+    /**
+     * 删除部门管理信息
+     * 
+     * @param deptId 部门ID
+     * @return 结果
+     */
+    public int deleteDeptById(Long deptId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java
new file mode 100644
index 0000000..9bc4f13
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java
@@ -0,0 +1,60 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+
+/**
+ * 字典 业务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysDictDataService
+{
+    /**
+     * 根据条件分页查询字典数据
+     * 
+     * @param dictData 字典数据信息
+     * @return 字典数据集合信息
+     */
+    public List<SysDictData> selectDictDataList(SysDictData dictData);
+
+    /**
+     * 根据字典类型和字典键值查询字典数据信息
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典键值
+     * @return 字典标签
+     */
+    public String selectDictLabel(String dictType, String dictValue);
+
+    /**
+     * 根据字典数据ID查询信息
+     * 
+     * @param dictCode 字典数据ID
+     * @return 字典数据
+     */
+    public SysDictData selectDictDataById(Long dictCode);
+
+    /**
+     * 批量删除字典数据信息
+     * 
+     * @param dictCodes 需要删除的字典数据ID
+     */
+    public void deleteDictDataByIds(Long[] dictCodes);
+
+    /**
+     * 新增保存字典数据信息
+     * 
+     * @param dictData 字典数据信息
+     * @return 结果
+     */
+    public int insertDictData(SysDictData dictData);
+
+    /**
+     * 修改保存字典数据信息
+     * 
+     * @param dictData 字典数据信息
+     * @return 结果
+     */
+    public int updateDictData(SysDictData dictData);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java
new file mode 100644
index 0000000..01c1c1d
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java
@@ -0,0 +1,98 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+import com.ruoyi.common.core.domain.entity.SysDictType;
+
+/**
+ * 字典 业务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysDictTypeService
+{
+    /**
+     * 根据条件分页查询字典类型
+     * 
+     * @param dictType 字典类型信息
+     * @return 字典类型集合信息
+     */
+    public List<SysDictType> selectDictTypeList(SysDictType dictType);
+
+    /**
+     * 根据所有字典类型
+     * 
+     * @return 字典类型集合信息
+     */
+    public List<SysDictType> selectDictTypeAll();
+
+    /**
+     * 根据字典类型查询字典数据
+     * 
+     * @param dictType 字典类型
+     * @return 字典数据集合信息
+     */
+    public List<SysDictData> selectDictDataByType(String dictType);
+
+    /**
+     * 根据字典类型ID查询信息
+     * 
+     * @param dictId 字典类型ID
+     * @return 字典类型
+     */
+    public SysDictType selectDictTypeById(Long dictId);
+
+    /**
+     * 根据字典类型查询信息
+     * 
+     * @param dictType 字典类型
+     * @return 字典类型
+     */
+    public SysDictType selectDictTypeByType(String dictType);
+
+    /**
+     * 批量删除字典信息
+     * 
+     * @param dictIds 需要删除的字典ID
+     */
+    public void deleteDictTypeByIds(Long[] dictIds);
+
+    /**
+     * 加载字典缓存数据
+     */
+    public void loadingDictCache();
+
+    /**
+     * 清空字典缓存数据
+     */
+    public void clearDictCache();
+
+    /**
+     * 重置字典缓存数据
+     */
+    public void resetDictCache();
+
+    /**
+     * 新增保存字典类型信息
+     * 
+     * @param dictType 字典类型信息
+     * @return 结果
+     */
+    public int insertDictType(SysDictType dictType);
+
+    /**
+     * 修改保存字典类型信息
+     * 
+     * @param dictType 字典类型信息
+     * @return 结果
+     */
+    public int updateDictType(SysDictType dictType);
+
+    /**
+     * 校验字典类型称是否唯一
+     * 
+     * @param dictType 字典类型
+     * @return 结果
+     */
+    public boolean checkDictTypeUnique(SysDictType dictType);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java
new file mode 100644
index 0000000..ce3151d
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java
@@ -0,0 +1,40 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysLogininfor;
+
+/**
+ * 系统访问日志情况信息 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysLogininforService
+{
+    /**
+     * 新增系统登录日志
+     * 
+     * @param logininfor 访问日志对象
+     */
+    public void insertLogininfor(SysLogininfor logininfor);
+
+    /**
+     * 查询系统登录日志集合
+     * 
+     * @param logininfor 访问日志对象
+     * @return 登录记录集合
+     */
+    public List<SysLogininfor> selectLogininforList(SysLogininfor logininfor);
+
+    /**
+     * 批量删除系统登录日志
+     * 
+     * @param infoIds 需要删除的登录日志ID
+     * @return 结果
+     */
+    public int deleteLogininforByIds(Long[] infoIds);
+
+    /**
+     * 清空系统登录日志
+     */
+    public void cleanLogininfor();
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
new file mode 100644
index 0000000..7d60696
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
@@ -0,0 +1,144 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import java.util.Set;
+import com.ruoyi.common.core.domain.TreeSelect;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.system.domain.vo.RouterVo;
+
+/**
+ * 菜单 业务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysMenuService
+{
+    /**
+     * 根据用户查询系统菜单列表
+     * 
+     * @param userId 用户ID
+     * @return 菜单列表
+     */
+    public List<SysMenu> selectMenuList(Long userId);
+
+    /**
+     * 根据用户查询系统菜单列表
+     * 
+     * @param menu 菜单信息
+     * @param userId 用户ID
+     * @return 菜单列表
+     */
+    public List<SysMenu> selectMenuList(SysMenu menu, Long userId);
+
+    /**
+     * 根据用户ID查询权限
+     * 
+     * @param userId 用户ID
+     * @return 权限列表
+     */
+    public Set<String> selectMenuPermsByUserId(Long userId);
+
+    /**
+     * 根据角色ID查询权限
+     * 
+     * @param roleId 角色ID
+     * @return 权限列表
+     */
+    public Set<String> selectMenuPermsByRoleId(Long roleId);
+
+    /**
+     * 根据用户ID查询菜单树信息
+     * 
+     * @param userId 用户ID
+     * @return 菜单列表
+     */
+    public List<SysMenu> selectMenuTreeByUserId(Long userId);
+
+    /**
+     * 根据角色ID查询菜单树信息
+     * 
+     * @param roleId 角色ID
+     * @return 选中菜单列表
+     */
+    public List<Long> selectMenuListByRoleId(Long roleId);
+
+    /**
+     * 构建前端路由所需要的菜单
+     * 
+     * @param menus 菜单列表
+     * @return 路由列表
+     */
+    public List<RouterVo> buildMenus(List<SysMenu> menus);
+
+    /**
+     * 构建前端所需要树结构
+     * 
+     * @param menus 菜单列表
+     * @return 树结构列表
+     */
+    public List<SysMenu> buildMenuTree(List<SysMenu> menus);
+
+    /**
+     * 构建前端所需要下拉树结构
+     * 
+     * @param menus 菜单列表
+     * @return 下拉树结构列表
+     */
+    public List<TreeSelect> buildMenuTreeSelect(List<SysMenu> menus);
+
+    /**
+     * 根据菜单ID查询信息
+     * 
+     * @param menuId 菜单ID
+     * @return 菜单信息
+     */
+    public SysMenu selectMenuById(Long menuId);
+
+    /**
+     * 是否存在菜单子节点
+     * 
+     * @param menuId 菜单ID
+     * @return 结果 true 存在 false 不存在
+     */
+    public boolean hasChildByMenuId(Long menuId);
+
+    /**
+     * 查询菜单是否存在角色
+     * 
+     * @param menuId 菜单ID
+     * @return 结果 true 存在 false 不存在
+     */
+    public boolean checkMenuExistRole(Long menuId);
+
+    /**
+     * 新增保存菜单信息
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    public int insertMenu(SysMenu menu);
+
+    /**
+     * 修改保存菜单信息
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    public int updateMenu(SysMenu menu);
+
+    /**
+     * 删除菜单管理信息
+     * 
+     * @param menuId 菜单ID
+     * @return 结果
+     */
+    public int deleteMenuById(Long menuId);
+
+    /**
+     * 校验菜单名称是否唯一
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    public boolean checkMenuNameUnique(SysMenu menu);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java
new file mode 100644
index 0000000..47ce1b7
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java
@@ -0,0 +1,60 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysNotice;
+
+/**
+ * 公告 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysNoticeService
+{
+    /**
+     * 查询公告信息
+     * 
+     * @param noticeId 公告ID
+     * @return 公告信息
+     */
+    public SysNotice selectNoticeById(Long noticeId);
+
+    /**
+     * 查询公告列表
+     * 
+     * @param notice 公告信息
+     * @return 公告集合
+     */
+    public List<SysNotice> selectNoticeList(SysNotice notice);
+
+    /**
+     * 新增公告
+     * 
+     * @param notice 公告信息
+     * @return 结果
+     */
+    public int insertNotice(SysNotice notice);
+
+    /**
+     * 修改公告
+     * 
+     * @param notice 公告信息
+     * @return 结果
+     */
+    public int updateNotice(SysNotice notice);
+
+    /**
+     * 删除公告信息
+     * 
+     * @param noticeId 公告ID
+     * @return 结果
+     */
+    public int deleteNoticeById(Long noticeId);
+    
+    /**
+     * 批量删除公告信息
+     * 
+     * @param noticeIds 需要删除的公告ID
+     * @return 结果
+     */
+    public int deleteNoticeByIds(Long[] noticeIds);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java
new file mode 100644
index 0000000..4fd8e5a
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java
@@ -0,0 +1,48 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysOperLog;
+
+/**
+ * 操作日志 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysOperLogService
+{
+    /**
+     * 新增操作日志
+     * 
+     * @param operLog 操作日志对象
+     */
+    public void insertOperlog(SysOperLog operLog);
+
+    /**
+     * 查询系统操作日志集合
+     * 
+     * @param operLog 操作日志对象
+     * @return 操作日志集合
+     */
+    public List<SysOperLog> selectOperLogList(SysOperLog operLog);
+
+    /**
+     * 批量删除系统操作日志
+     * 
+     * @param operIds 需要删除的操作日志ID
+     * @return 结果
+     */
+    public int deleteOperLogByIds(Long[] operIds);
+
+    /**
+     * 查询操作日志详细
+     * 
+     * @param operId 操作ID
+     * @return 操作日志对象
+     */
+    public SysOperLog selectOperLogById(Long operId);
+
+    /**
+     * 清空操作日志
+     */
+    public void cleanOperLog();
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java
new file mode 100644
index 0000000..84779bf
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java
@@ -0,0 +1,99 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysPost;
+
+/**
+ * 岗位信息 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysPostService
+{
+    /**
+     * 查询岗位信息集合
+     * 
+     * @param post 岗位信息
+     * @return 岗位列表
+     */
+    public List<SysPost> selectPostList(SysPost post);
+
+    /**
+     * 查询所有岗位
+     * 
+     * @return 岗位列表
+     */
+    public List<SysPost> selectPostAll();
+
+    /**
+     * 通过岗位ID查询岗位信息
+     * 
+     * @param postId 岗位ID
+     * @return 角色对象信息
+     */
+    public SysPost selectPostById(Long postId);
+
+    /**
+     * 根据用户ID获取岗位选择框列表
+     * 
+     * @param userId 用户ID
+     * @return 选中岗位ID列表
+     */
+    public List<Long> selectPostListByUserId(Long userId);
+
+    /**
+     * 校验岗位名称
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    public boolean checkPostNameUnique(SysPost post);
+
+    /**
+     * 校验岗位编码
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    public boolean checkPostCodeUnique(SysPost post);
+
+    /**
+     * 通过岗位ID查询岗位使用数量
+     * 
+     * @param postId 岗位ID
+     * @return 结果
+     */
+    public int countUserPostById(Long postId);
+
+    /**
+     * 删除岗位信息
+     * 
+     * @param postId 岗位ID
+     * @return 结果
+     */
+    public int deletePostById(Long postId);
+
+    /**
+     * 批量删除岗位信息
+     * 
+     * @param postIds 需要删除的岗位ID
+     * @return 结果
+     */
+    public int deletePostByIds(Long[] postIds);
+
+    /**
+     * 新增保存岗位信息
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    public int insertPost(SysPost post);
+
+    /**
+     * 修改保存岗位信息
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    public int updatePost(SysPost post);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java
new file mode 100644
index 0000000..9185cce
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java
@@ -0,0 +1,173 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import java.util.Set;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.system.domain.SysUserRole;
+
+/**
+ * 角色业务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysRoleService
+{
+    /**
+     * 根据条件分页查询角色数据
+     * 
+     * @param role 角色信息
+     * @return 角色数据集合信息
+     */
+    public List<SysRole> selectRoleList(SysRole role);
+
+    /**
+     * 根据用户ID查询角色列表
+     * 
+     * @param userId 用户ID
+     * @return 角色列表
+     */
+    public List<SysRole> selectRolesByUserId(Long userId);
+
+    /**
+     * 根据用户ID查询角色权限
+     * 
+     * @param userId 用户ID
+     * @return 权限列表
+     */
+    public Set<String> selectRolePermissionByUserId(Long userId);
+
+    /**
+     * 查询所有角色
+     * 
+     * @return 角色列表
+     */
+    public List<SysRole> selectRoleAll();
+
+    /**
+     * 根据用户ID获取角色选择框列表
+     * 
+     * @param userId 用户ID
+     * @return 选中角色ID列表
+     */
+    public List<Long> selectRoleListByUserId(Long userId);
+
+    /**
+     * 通过角色ID查询角色
+     * 
+     * @param roleId 角色ID
+     * @return 角色对象信息
+     */
+    public SysRole selectRoleById(Long roleId);
+
+    /**
+     * 校验角色名称是否唯一
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    public boolean checkRoleNameUnique(SysRole role);
+
+    /**
+     * 校验角色权限是否唯一
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    public boolean checkRoleKeyUnique(SysRole role);
+
+    /**
+     * 校验角色是否允许操作
+     * 
+     * @param role 角色信息
+     */
+    public void checkRoleAllowed(SysRole role);
+
+    /**
+     * 校验角色是否有数据权限
+     * 
+     * @param roleIds 角色id
+     */
+    public void checkRoleDataScope(Long... roleIds);
+
+    /**
+     * 通过角色ID查询角色使用数量
+     * 
+     * @param roleId 角色ID
+     * @return 结果
+     */
+    public int countUserRoleByRoleId(Long roleId);
+
+    /**
+     * 新增保存角色信息
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    public int insertRole(SysRole role);
+
+    /**
+     * 修改保存角色信息
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    public int updateRole(SysRole role);
+
+    /**
+     * 修改角色状态
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    public int updateRoleStatus(SysRole role);
+
+    /**
+     * 修改数据权限信息
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    public int authDataScope(SysRole role);
+
+    /**
+     * 通过角色ID删除角色
+     * 
+     * @param roleId 角色ID
+     * @return 结果
+     */
+    public int deleteRoleById(Long roleId);
+
+    /**
+     * 批量删除角色信息
+     * 
+     * @param roleIds 需要删除的角色ID
+     * @return 结果
+     */
+    public int deleteRoleByIds(Long[] roleIds);
+
+    /**
+     * 取消授权用户角色
+     * 
+     * @param userRole 用户和角色关联信息
+     * @return 结果
+     */
+    public int deleteAuthUser(SysUserRole userRole);
+
+    /**
+     * 批量取消授权用户角色
+     * 
+     * @param roleId 角色ID
+     * @param userIds 需要取消授权的用户数据ID
+     * @return 结果
+     */
+    public int deleteAuthUsers(Long roleId, Long[] userIds);
+
+    /**
+     * 批量选择授权用户角色
+     * 
+     * @param roleId 角色ID
+     * @param userIds 需要删除的用户数据ID
+     * @return 结果
+     */
+    public int insertAuthUsers(Long roleId, Long[] userIds);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java
new file mode 100644
index 0000000..8eb5448
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java
@@ -0,0 +1,48 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.system.domain.SysUserOnline;
+
+/**
+ * 在线用户 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysUserOnlineService
+{
+    /**
+     * 通过登录地址查询信息
+     * 
+     * @param ipaddr 登录地址
+     * @param user 用户信息
+     * @return 在线用户信息
+     */
+    public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user);
+
+    /**
+     * 通过用户名称查询信息
+     * 
+     * @param userName 用户名称
+     * @param user 用户信息
+     * @return 在线用户信息
+     */
+    public SysUserOnline selectOnlineByUserName(String userName, LoginUser user);
+
+    /**
+     * 通过登录地址/用户名称查询信息
+     * 
+     * @param ipaddr 登录地址
+     * @param userName 用户名称
+     * @param user 用户信息
+     * @return 在线用户信息
+     */
+    public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user);
+
+    /**
+     * 设置在线用户信息
+     * 
+     * @param user 用户信息
+     * @return 在线用户
+     */
+    public SysUserOnline loginUserToUserOnline(LoginUser user);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
new file mode 100644
index 0000000..10bc2ab
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
@@ -0,0 +1,206 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.common.core.domain.entity.SysUser;
+
+/**
+ * 用户 业务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysUserService
+{
+    /**
+     * 根据条件分页查询用户列表
+     * 
+     * @param user 用户信息
+     * @return 用户信息集合信息
+     */
+    public List<SysUser> selectUserList(SysUser user);
+
+    /**
+     * 根据条件分页查询已分配用户角色列表
+     * 
+     * @param user 用户信息
+     * @return 用户信息集合信息
+     */
+    public List<SysUser> selectAllocatedList(SysUser user);
+
+    /**
+     * 根据条件分页查询未分配用户角色列表
+     * 
+     * @param user 用户信息
+     * @return 用户信息集合信息
+     */
+    public List<SysUser> selectUnallocatedList(SysUser user);
+
+    /**
+     * 通过用户名查询用户
+     * 
+     * @param userName 用户名
+     * @return 用户对象信息
+     */
+    public SysUser selectUserByUserName(String userName);
+
+    /**
+     * 通过用户ID查询用户
+     * 
+     * @param userId 用户ID
+     * @return 用户对象信息
+     */
+    public SysUser selectUserById(Long userId);
+
+    /**
+     * 根据用户ID查询用户所属角色组
+     * 
+     * @param userName 用户名
+     * @return 结果
+     */
+    public String selectUserRoleGroup(String userName);
+
+    /**
+     * 根据用户ID查询用户所属岗位组
+     * 
+     * @param userName 用户名
+     * @return 结果
+     */
+    public String selectUserPostGroup(String userName);
+
+    /**
+     * 校验用户名称是否唯一
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public boolean checkUserNameUnique(SysUser user);
+
+    /**
+     * 校验手机号码是否唯一
+     *
+     * @param user 用户信息
+     * @return 结果
+     */
+    public boolean checkPhoneUnique(SysUser user);
+
+    /**
+     * 校验email是否唯一
+     *
+     * @param user 用户信息
+     * @return 结果
+     */
+    public boolean checkEmailUnique(SysUser user);
+
+    /**
+     * 校验用户是否允许操作
+     * 
+     * @param user 用户信息
+     */
+    public void checkUserAllowed(SysUser user);
+
+    /**
+     * 校验用户是否有数据权限
+     * 
+     * @param userId 用户id
+     */
+    public void checkUserDataScope(Long userId);
+
+    /**
+     * 新增用户信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public int insertUser(SysUser user);
+
+    /**
+     * 注册用户信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public boolean registerUser(SysUser user);
+
+    /**
+     * 修改用户信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public int updateUser(SysUser user);
+
+    /**
+     * 用户授权角色
+     * 
+     * @param userId 用户ID
+     * @param roleIds 角色组
+     */
+    public void insertUserAuth(Long userId, Long[] roleIds);
+
+    /**
+     * 修改用户状态
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public int updateUserStatus(SysUser user);
+
+    /**
+     * 修改用户基本信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public int updateUserProfile(SysUser user);
+
+    /**
+     * 修改用户头像
+     * 
+     * @param userName 用户名
+     * @param avatar 头像地址
+     * @return 结果
+     */
+    public boolean updateUserAvatar(String userName, String avatar);
+
+    /**
+     * 重置用户密码
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    public int resetPwd(SysUser user);
+
+    /**
+     * 重置用户密码
+     * 
+     * @param userName 用户名
+     * @param password 密码
+     * @return 结果
+     */
+    public int resetUserPwd(String userName, String password);
+
+    /**
+     * 通过用户ID删除用户
+     * 
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public int deleteUserById(Long userId);
+
+    /**
+     * 批量删除用户信息
+     * 
+     * @param userIds 需要删除的用户ID
+     * @return 结果
+     */
+    public int deleteUserByIds(Long[] userIds);
+
+    /**
+     * 导入用户数据
+     * 
+     * @param userList 用户数据列表
+     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
+     * @param operName 操作用户
+     * @return 结果
+     */
+    public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java
new file mode 100644
index 0000000..83750e7
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java
@@ -0,0 +1,232 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.Collection;
+import java.util.List;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.annotation.DataSource;
+import com.ruoyi.common.constant.CacheConstants;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.enums.DataSourceType;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysConfig;
+import com.ruoyi.system.mapper.SysConfigMapper;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 参数配置 服务层实现
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysConfigServiceImpl implements ISysConfigService
+{
+    @Autowired
+    private SysConfigMapper configMapper;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 项目启动时,初始化参数到缓存
+     */
+    @PostConstruct
+    public void init()
+    {
+        loadingConfigCache();
+    }
+
+    /**
+     * 查询参数配置信息
+     * 
+     * @param configId 参数配置ID
+     * @return 参数配置信息
+     */
+    @Override
+    @DataSource(DataSourceType.MASTER)
+    public SysConfig selectConfigById(Long configId)
+    {
+        SysConfig config = new SysConfig();
+        config.setConfigId(configId);
+        return configMapper.selectConfig(config);
+    }
+
+    /**
+     * 根据键名查询参数配置信息
+     * 
+     * @param configKey 参数key
+     * @return 参数键值
+     */
+    @Override
+    public String selectConfigByKey(String configKey)
+    {
+        String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));
+        if (StringUtils.isNotEmpty(configValue))
+        {
+            return configValue;
+        }
+        SysConfig config = new SysConfig();
+        config.setConfigKey(configKey);
+        SysConfig retConfig = configMapper.selectConfig(config);
+        if (StringUtils.isNotNull(retConfig))
+        {
+            redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
+            return retConfig.getConfigValue();
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 获取验证码开关
+     * 
+     * @return true开启,false关闭
+     */
+    @Override
+    public boolean selectCaptchaEnabled()
+    {
+        String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled");
+        if (StringUtils.isEmpty(captchaEnabled))
+        {
+            return true;
+        }
+        return Convert.toBool(captchaEnabled);
+    }
+
+    /**
+     * 查询参数配置列表
+     * 
+     * @param config 参数配置信息
+     * @return 参数配置集合
+     */
+    @Override
+    public List<SysConfig> selectConfigList(SysConfig config)
+    {
+        return configMapper.selectConfigList(config);
+    }
+
+    /**
+     * 新增参数配置
+     * 
+     * @param config 参数配置信息
+     * @return 结果
+     */
+    @Override
+    public int insertConfig(SysConfig config)
+    {
+        int row = configMapper.insertConfig(config);
+        if (row > 0)
+        {
+            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
+        }
+        return row;
+    }
+
+    /**
+     * 修改参数配置
+     * 
+     * @param config 参数配置信息
+     * @return 结果
+     */
+    @Override
+    public int updateConfig(SysConfig config)
+    {
+        SysConfig temp = configMapper.selectConfigById(config.getConfigId());
+        if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey()))
+        {
+            redisCache.deleteObject(getCacheKey(temp.getConfigKey()));
+        }
+
+        int row = configMapper.updateConfig(config);
+        if (row > 0)
+        {
+            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
+        }
+        return row;
+    }
+
+    /**
+     * 批量删除参数信息
+     * 
+     * @param configIds 需要删除的参数ID
+     */
+    @Override
+    public void deleteConfigByIds(Long[] configIds)
+    {
+        for (Long configId : configIds)
+        {
+            SysConfig config = selectConfigById(configId);
+            if (StringUtils.equals(UserConstants.YES, config.getConfigType()))
+            {
+                throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey()));
+            }
+            configMapper.deleteConfigById(configId);
+            redisCache.deleteObject(getCacheKey(config.getConfigKey()));
+        }
+    }
+
+    /**
+     * 加载参数缓存数据
+     */
+    @Override
+    public void loadingConfigCache()
+    {
+        List<SysConfig> configsList = configMapper.selectConfigList(new SysConfig());
+        for (SysConfig config : configsList)
+        {
+            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
+        }
+    }
+
+    /**
+     * 清空参数缓存数据
+     */
+    @Override
+    public void clearConfigCache()
+    {
+        Collection<String> keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*");
+        redisCache.deleteObject(keys);
+    }
+
+    /**
+     * 重置参数缓存数据
+     */
+    @Override
+    public void resetConfigCache()
+    {
+        clearConfigCache();
+        loadingConfigCache();
+    }
+
+    /**
+     * 校验参数键名是否唯一
+     * 
+     * @param config 参数配置信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkConfigKeyUnique(SysConfig config)
+    {
+        Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId();
+        SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey());
+        if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 设置cache key
+     * 
+     * @param configKey 参数键
+     * @return 缓存键key
+     */
+    private String getCacheKey(String configKey)
+    {
+        return CacheConstants.SYS_CONFIG_KEY + configKey;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java
new file mode 100644
index 0000000..54b605d
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java
@@ -0,0 +1,338 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.annotation.DataScope;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.TreeSelect;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.system.mapper.SysDeptMapper;
+import com.ruoyi.system.mapper.SysRoleMapper;
+import com.ruoyi.system.service.ISysDeptService;
+
+/**
+ * 部门管理 服务实现
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysDeptServiceImpl implements ISysDeptService
+{
+    @Autowired
+    private SysDeptMapper deptMapper;
+
+    @Autowired
+    private SysRoleMapper roleMapper;
+
+    /**
+     * 查询部门管理数据
+     * 
+     * @param dept 部门信息
+     * @return 部门信息集合
+     */
+    @Override
+    @DataScope(deptAlias = "d")
+    public List<SysDept> selectDeptList(SysDept dept)
+    {
+        return deptMapper.selectDeptList(dept);
+    }
+
+    /**
+     * 查询部门树结构信息
+     * 
+     * @param dept 部门信息
+     * @return 部门树信息集合
+     */
+    @Override
+    public List<TreeSelect> selectDeptTreeList(SysDept dept)
+    {
+        List<SysDept> depts = SpringUtils.getAopProxy(this).selectDeptList(dept);
+        return buildDeptTreeSelect(depts);
+    }
+
+    /**
+     * 构建前端所需要树结构
+     * 
+     * @param depts 部门列表
+     * @return 树结构列表
+     */
+    @Override
+    public List<SysDept> buildDeptTree(List<SysDept> depts)
+    {
+        List<SysDept> returnList = new ArrayList<SysDept>();
+        List<Long> tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList());
+        for (SysDept dept : depts)
+        {
+            // 如果是顶级节点, 遍历该父节点的所有子节点
+            if (!tempList.contains(dept.getParentId()))
+            {
+                recursionFn(depts, dept);
+                returnList.add(dept);
+            }
+        }
+        if (returnList.isEmpty())
+        {
+            returnList = depts;
+        }
+        return returnList;
+    }
+
+    /**
+     * 构建前端所需要下拉树结构
+     * 
+     * @param depts 部门列表
+     * @return 下拉树结构列表
+     */
+    @Override
+    public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts)
+    {
+        List<SysDept> deptTrees = buildDeptTree(depts);
+        return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
+    }
+
+    /**
+     * 根据角色ID查询部门树信息
+     * 
+     * @param roleId 角色ID
+     * @return 选中部门列表
+     */
+    @Override
+    public List<Long> selectDeptListByRoleId(Long roleId)
+    {
+        SysRole role = roleMapper.selectRoleById(roleId);
+        return deptMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly());
+    }
+
+    /**
+     * 根据部门ID查询信息
+     * 
+     * @param deptId 部门ID
+     * @return 部门信息
+     */
+    @Override
+    public SysDept selectDeptById(Long deptId)
+    {
+        return deptMapper.selectDeptById(deptId);
+    }
+
+    /**
+     * 根据ID查询所有子部门(正常状态)
+     * 
+     * @param deptId 部门ID
+     * @return 子部门数
+     */
+    @Override
+    public int selectNormalChildrenDeptById(Long deptId)
+    {
+        return deptMapper.selectNormalChildrenDeptById(deptId);
+    }
+
+    /**
+     * 是否存在子节点
+     * 
+     * @param deptId 部门ID
+     * @return 结果
+     */
+    @Override
+    public boolean hasChildByDeptId(Long deptId)
+    {
+        int result = deptMapper.hasChildByDeptId(deptId);
+        return result > 0;
+    }
+
+    /**
+     * 查询部门是否存在用户
+     * 
+     * @param deptId 部门ID
+     * @return 结果 true 存在 false 不存在
+     */
+    @Override
+    public boolean checkDeptExistUser(Long deptId)
+    {
+        int result = deptMapper.checkDeptExistUser(deptId);
+        return result > 0;
+    }
+
+    /**
+     * 校验部门名称是否唯一
+     * 
+     * @param dept 部门信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkDeptNameUnique(SysDept dept)
+    {
+        Long deptId = StringUtils.isNull(dept.getDeptId()) ? -1L : dept.getDeptId();
+        SysDept info = deptMapper.checkDeptNameUnique(dept.getDeptName(), dept.getParentId());
+        if (StringUtils.isNotNull(info) && info.getDeptId().longValue() != deptId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 校验部门是否有数据权限
+     * 
+     * @param deptId 部门id
+     */
+    @Override
+    public void checkDeptDataScope(Long deptId)
+    {
+        if (!SysUser.isAdmin(SecurityUtils.getUserId()) && StringUtils.isNotNull(deptId))
+        {
+            SysDept dept = new SysDept();
+            dept.setDeptId(deptId);
+            List<SysDept> depts = SpringUtils.getAopProxy(this).selectDeptList(dept);
+            if (StringUtils.isEmpty(depts))
+            {
+                throw new ServiceException("没有权限访问部门数据!");
+            }
+        }
+    }
+
+    /**
+     * 新增保存部门信息
+     * 
+     * @param dept 部门信息
+     * @return 结果
+     */
+    @Override
+    public int insertDept(SysDept dept)
+    {
+        SysDept info = deptMapper.selectDeptById(dept.getParentId());
+        // 如果父节点不为正常状态,则不允许新增子节点
+        if (!UserConstants.DEPT_NORMAL.equals(info.getStatus()))
+        {
+            throw new ServiceException("部门停用,不允许新增");
+        }
+        dept.setAncestors(info.getAncestors() + "," + dept.getParentId());
+        return deptMapper.insertDept(dept);
+    }
+
+    /**
+     * 修改保存部门信息
+     * 
+     * @param dept 部门信息
+     * @return 结果
+     */
+    @Override
+    public int updateDept(SysDept dept)
+    {
+        SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId());
+        SysDept oldDept = deptMapper.selectDeptById(dept.getDeptId());
+        if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept))
+        {
+            String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId();
+            String oldAncestors = oldDept.getAncestors();
+            dept.setAncestors(newAncestors);
+            updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors);
+        }
+        int result = deptMapper.updateDept(dept);
+        if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors())
+                && !StringUtils.equals("0", dept.getAncestors()))
+        {
+            // 如果该部门是启用状态,则启用该部门的所有上级部门
+            updateParentDeptStatusNormal(dept);
+        }
+        return result;
+    }
+
+    /**
+     * 修改该部门的父级部门状态
+     * 
+     * @param dept 当前部门
+     */
+    private void updateParentDeptStatusNormal(SysDept dept)
+    {
+        String ancestors = dept.getAncestors();
+        Long[] deptIds = Convert.toLongArray(ancestors);
+        deptMapper.updateDeptStatusNormal(deptIds);
+    }
+
+    /**
+     * 修改子元素关系
+     * 
+     * @param deptId 被修改的部门ID
+     * @param newAncestors 新的父ID集合
+     * @param oldAncestors 旧的父ID集合
+     */
+    public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors)
+    {
+        List<SysDept> children = deptMapper.selectChildrenDeptById(deptId);
+        for (SysDept child : children)
+        {
+            child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
+        }
+        if (children.size() > 0)
+        {
+            deptMapper.updateDeptChildren(children);
+        }
+    }
+
+    /**
+     * 删除部门管理信息
+     * 
+     * @param deptId 部门ID
+     * @return 结果
+     */
+    @Override
+    public int deleteDeptById(Long deptId)
+    {
+        return deptMapper.deleteDeptById(deptId);
+    }
+
+    /**
+     * 递归列表
+     */
+    private void recursionFn(List<SysDept> list, SysDept t)
+    {
+        // 得到子节点列表
+        List<SysDept> childList = getChildList(list, t);
+        t.setChildren(childList);
+        for (SysDept tChild : childList)
+        {
+            if (hasChild(list, tChild))
+            {
+                recursionFn(list, tChild);
+            }
+        }
+    }
+
+    /**
+     * 得到子节点列表
+     */
+    private List<SysDept> getChildList(List<SysDept> list, SysDept t)
+    {
+        List<SysDept> tlist = new ArrayList<SysDept>();
+        Iterator<SysDept> it = list.iterator();
+        while (it.hasNext())
+        {
+            SysDept n = (SysDept) it.next();
+            if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue())
+            {
+                tlist.add(n);
+            }
+        }
+        return tlist;
+    }
+
+    /**
+     * 判断是否有子节点
+     */
+    private boolean hasChild(List<SysDept> list, SysDept t)
+    {
+        return getChildList(list, t).size() > 0;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java
new file mode 100644
index 0000000..fced569
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java
@@ -0,0 +1,111 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+import com.ruoyi.common.utils.DictUtils;
+import com.ruoyi.system.mapper.SysDictDataMapper;
+import com.ruoyi.system.service.ISysDictDataService;
+
+/**
+ * 字典 业务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysDictDataServiceImpl implements ISysDictDataService
+{
+    @Autowired
+    private SysDictDataMapper dictDataMapper;
+
+    /**
+     * 根据条件分页查询字典数据
+     * 
+     * @param dictData 字典数据信息
+     * @return 字典数据集合信息
+     */
+    @Override
+    public List<SysDictData> selectDictDataList(SysDictData dictData)
+    {
+        return dictDataMapper.selectDictDataList(dictData);
+    }
+
+    /**
+     * 根据字典类型和字典键值查询字典数据信息
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典键值
+     * @return 字典标签
+     */
+    @Override
+    public String selectDictLabel(String dictType, String dictValue)
+    {
+        return dictDataMapper.selectDictLabel(dictType, dictValue);
+    }
+
+    /**
+     * 根据字典数据ID查询信息
+     * 
+     * @param dictCode 字典数据ID
+     * @return 字典数据
+     */
+    @Override
+    public SysDictData selectDictDataById(Long dictCode)
+    {
+        return dictDataMapper.selectDictDataById(dictCode);
+    }
+
+    /**
+     * 批量删除字典数据信息
+     * 
+     * @param dictCodes 需要删除的字典数据ID
+     */
+    @Override
+    public void deleteDictDataByIds(Long[] dictCodes)
+    {
+        for (Long dictCode : dictCodes)
+        {
+            SysDictData data = selectDictDataById(dictCode);
+            dictDataMapper.deleteDictDataById(dictCode);
+            List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(data.getDictType());
+            DictUtils.setDictCache(data.getDictType(), dictDatas);
+        }
+    }
+
+    /**
+     * 新增保存字典数据信息
+     * 
+     * @param data 字典数据信息
+     * @return 结果
+     */
+    @Override
+    public int insertDictData(SysDictData data)
+    {
+        int row = dictDataMapper.insertDictData(data);
+        if (row > 0)
+        {
+            List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(data.getDictType());
+            DictUtils.setDictCache(data.getDictType(), dictDatas);
+        }
+        return row;
+    }
+
+    /**
+     * 修改保存字典数据信息
+     * 
+     * @param data 字典数据信息
+     * @return 结果
+     */
+    @Override
+    public int updateDictData(SysDictData data)
+    {
+        int row = dictDataMapper.updateDictData(data);
+        if (row > 0)
+        {
+            List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(data.getDictType());
+            DictUtils.setDictCache(data.getDictType(), dictDatas);
+        }
+        return row;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java
new file mode 100644
index 0000000..37928c4
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java
@@ -0,0 +1,223 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+import com.ruoyi.common.core.domain.entity.SysDictType;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.DictUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.mapper.SysDictDataMapper;
+import com.ruoyi.system.mapper.SysDictTypeMapper;
+import com.ruoyi.system.service.ISysDictTypeService;
+
+/**
+ * 字典 业务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysDictTypeServiceImpl implements ISysDictTypeService
+{
+    @Autowired
+    private SysDictTypeMapper dictTypeMapper;
+
+    @Autowired
+    private SysDictDataMapper dictDataMapper;
+
+    /**
+     * 项目启动时,初始化字典到缓存
+     */
+    @PostConstruct
+    public void init()
+    {
+        loadingDictCache();
+    }
+
+    /**
+     * 根据条件分页查询字典类型
+     * 
+     * @param dictType 字典类型信息
+     * @return 字典类型集合信息
+     */
+    @Override
+    public List<SysDictType> selectDictTypeList(SysDictType dictType)
+    {
+        return dictTypeMapper.selectDictTypeList(dictType);
+    }
+
+    /**
+     * 根据所有字典类型
+     * 
+     * @return 字典类型集合信息
+     */
+    @Override
+    public List<SysDictType> selectDictTypeAll()
+    {
+        return dictTypeMapper.selectDictTypeAll();
+    }
+
+    /**
+     * 根据字典类型查询字典数据
+     * 
+     * @param dictType 字典类型
+     * @return 字典数据集合信息
+     */
+    @Override
+    public List<SysDictData> selectDictDataByType(String dictType)
+    {
+        List<SysDictData> dictDatas = DictUtils.getDictCache(dictType);
+        if (StringUtils.isNotEmpty(dictDatas))
+        {
+            return dictDatas;
+        }
+        dictDatas = dictDataMapper.selectDictDataByType(dictType);
+        if (StringUtils.isNotEmpty(dictDatas))
+        {
+            DictUtils.setDictCache(dictType, dictDatas);
+            return dictDatas;
+        }
+        return null;
+    }
+
+    /**
+     * 根据字典类型ID查询信息
+     * 
+     * @param dictId 字典类型ID
+     * @return 字典类型
+     */
+    @Override
+    public SysDictType selectDictTypeById(Long dictId)
+    {
+        return dictTypeMapper.selectDictTypeById(dictId);
+    }
+
+    /**
+     * 根据字典类型查询信息
+     * 
+     * @param dictType 字典类型
+     * @return 字典类型
+     */
+    @Override
+    public SysDictType selectDictTypeByType(String dictType)
+    {
+        return dictTypeMapper.selectDictTypeByType(dictType);
+    }
+
+    /**
+     * 批量删除字典类型信息
+     * 
+     * @param dictIds 需要删除的字典ID
+     */
+    @Override
+    public void deleteDictTypeByIds(Long[] dictIds)
+    {
+        for (Long dictId : dictIds)
+        {
+            SysDictType dictType = selectDictTypeById(dictId);
+            if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0)
+            {
+                throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName()));
+            }
+            dictTypeMapper.deleteDictTypeById(dictId);
+            DictUtils.removeDictCache(dictType.getDictType());
+        }
+    }
+
+    /**
+     * 加载字典缓存数据
+     */
+    @Override
+    public void loadingDictCache()
+    {
+        SysDictData dictData = new SysDictData();
+        dictData.setStatus("0");
+        Map<String, List<SysDictData>> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType));
+        for (Map.Entry<String, List<SysDictData>> entry : dictDataMap.entrySet())
+        {
+            DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList()));
+        }
+    }
+
+    /**
+     * 清空字典缓存数据
+     */
+    @Override
+    public void clearDictCache()
+    {
+        DictUtils.clearDictCache();
+    }
+
+    /**
+     * 重置字典缓存数据
+     */
+    @Override
+    public void resetDictCache()
+    {
+        clearDictCache();
+        loadingDictCache();
+    }
+
+    /**
+     * 新增保存字典类型信息
+     * 
+     * @param dict 字典类型信息
+     * @return 结果
+     */
+    @Override
+    public int insertDictType(SysDictType dict)
+    {
+        int row = dictTypeMapper.insertDictType(dict);
+        if (row > 0)
+        {
+            DictUtils.setDictCache(dict.getDictType(), null);
+        }
+        return row;
+    }
+
+    /**
+     * 修改保存字典类型信息
+     * 
+     * @param dict 字典类型信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int updateDictType(SysDictType dict)
+    {
+        SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId());
+        dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType());
+        int row = dictTypeMapper.updateDictType(dict);
+        if (row > 0)
+        {
+            List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType());
+            DictUtils.setDictCache(dict.getDictType(), dictDatas);
+        }
+        return row;
+    }
+
+    /**
+     * 校验字典类型称是否唯一
+     * 
+     * @param dict 字典类型
+     * @return 结果
+     */
+    @Override
+    public boolean checkDictTypeUnique(SysDictType dict)
+    {
+        Long dictId = StringUtils.isNull(dict.getDictId()) ? -1L : dict.getDictId();
+        SysDictType dictType = dictTypeMapper.checkDictTypeUnique(dict.getDictType());
+        if (StringUtils.isNotNull(dictType) && dictType.getDictId().longValue() != dictId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java
new file mode 100644
index 0000000..216aecb
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java
@@ -0,0 +1,65 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.domain.SysLogininfor;
+import com.ruoyi.system.mapper.SysLogininforMapper;
+import com.ruoyi.system.service.ISysLogininforService;
+
+/**
+ * 系统访问日志情况信息 服务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysLogininforServiceImpl implements ISysLogininforService
+{
+
+    @Autowired
+    private SysLogininforMapper logininforMapper;
+
+    /**
+     * 新增系统登录日志
+     * 
+     * @param logininfor 访问日志对象
+     */
+    @Override
+    public void insertLogininfor(SysLogininfor logininfor)
+    {
+        logininforMapper.insertLogininfor(logininfor);
+    }
+
+    /**
+     * 查询系统登录日志集合
+     * 
+     * @param logininfor 访问日志对象
+     * @return 登录记录集合
+     */
+    @Override
+    public List<SysLogininfor> selectLogininforList(SysLogininfor logininfor)
+    {
+        return logininforMapper.selectLogininforList(logininfor);
+    }
+
+    /**
+     * 批量删除系统登录日志
+     * 
+     * @param infoIds 需要删除的登录日志ID
+     * @return 结果
+     */
+    @Override
+    public int deleteLogininforByIds(Long[] infoIds)
+    {
+        return logininforMapper.deleteLogininforByIds(infoIds);
+    }
+
+    /**
+     * 清空系统登录日志
+     */
+    @Override
+    public void cleanLogininfor()
+    {
+        logininforMapper.cleanLogininfor();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
new file mode 100644
index 0000000..78a7830
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
@@ -0,0 +1,543 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.TreeSelect;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.vo.MetaVo;
+import com.ruoyi.system.domain.vo.RouterVo;
+import com.ruoyi.system.mapper.SysMenuMapper;
+import com.ruoyi.system.mapper.SysRoleMapper;
+import com.ruoyi.system.mapper.SysRoleMenuMapper;
+import com.ruoyi.system.service.ISysMenuService;
+
+/**
+ * 菜单 业务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysMenuServiceImpl implements ISysMenuService
+{
+    public static final String PREMISSION_STRING = "perms[\"{0}\"]";
+
+    @Autowired
+    private SysMenuMapper menuMapper;
+
+    @Autowired
+    private SysRoleMapper roleMapper;
+
+    @Autowired
+    private SysRoleMenuMapper roleMenuMapper;
+
+    /**
+     * 根据用户查询系统菜单列表
+     * 
+     * @param userId 用户ID
+     * @return 菜单列表
+     */
+    @Override
+    public List<SysMenu> selectMenuList(Long userId)
+    {
+        return selectMenuList(new SysMenu(), userId);
+    }
+
+    /**
+     * 查询系统菜单列表
+     * 
+     * @param menu 菜单信息
+     * @return 菜单列表
+     */
+    @Override
+    public List<SysMenu> selectMenuList(SysMenu menu, Long userId)
+    {
+        List<SysMenu> menuList = null;
+        // 管理员显示所有菜单信息
+        if (SysUser.isAdmin(userId))
+        {
+            menuList = menuMapper.selectMenuList(menu);
+        }
+        else
+        {
+            menu.getParams().put("userId", userId);
+            menuList = menuMapper.selectMenuListByUserId(menu);
+        }
+        return menuList;
+    }
+
+    /**
+     * 根据用户ID查询权限
+     * 
+     * @param userId 用户ID
+     * @return 权限列表
+     */
+    @Override
+    public Set<String> selectMenuPermsByUserId(Long userId)
+    {
+        List<String> perms = menuMapper.selectMenuPermsByUserId(userId);
+        Set<String> permsSet = new HashSet<>();
+        for (String perm : perms)
+        {
+            if (StringUtils.isNotEmpty(perm))
+            {
+                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
+            }
+        }
+        return permsSet;
+    }
+
+    /**
+     * 根据角色ID查询权限
+     * 
+     * @param roleId 角色ID
+     * @return 权限列表
+     */
+    @Override
+    public Set<String> selectMenuPermsByRoleId(Long roleId)
+    {
+        List<String> perms = menuMapper.selectMenuPermsByRoleId(roleId);
+        Set<String> permsSet = new HashSet<>();
+        for (String perm : perms)
+        {
+            if (StringUtils.isNotEmpty(perm))
+            {
+                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
+            }
+        }
+        return permsSet;
+    }
+
+    /**
+     * 根据用户ID查询菜单
+     * 
+     * @param userId 用户名称
+     * @return 菜单列表
+     */
+    @Override
+    public List<SysMenu> selectMenuTreeByUserId(Long userId)
+    {
+        List<SysMenu> menus = null;
+        if (SecurityUtils.isAdmin(userId))
+        {
+            menus = menuMapper.selectMenuTreeAll();
+        }
+        else
+        {
+            menus = menuMapper.selectMenuTreeByUserId(userId);
+        }
+        return getChildPerms(menus, 0);
+    }
+
+    /**
+     * 根据角色ID查询菜单树信息
+     * 
+     * @param roleId 角色ID
+     * @return 选中菜单列表
+     */
+    @Override
+    public List<Long> selectMenuListByRoleId(Long roleId)
+    {
+        SysRole role = roleMapper.selectRoleById(roleId);
+        return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly());
+    }
+
+    /**
+     * 构建前端路由所需要的菜单
+     * 
+     * @param menus 菜单列表
+     * @return 路由列表
+     */
+    @Override
+    public List<RouterVo> buildMenus(List<SysMenu> menus)
+    {
+        List<RouterVo> routers = new LinkedList<RouterVo>();
+        for (SysMenu menu : menus)
+        {
+            RouterVo router = new RouterVo();
+            router.setHidden("1".equals(menu.getVisible()));
+            router.setName(getRouteName(menu));
+            router.setPath(getRouterPath(menu));
+            router.setComponent(getComponent(menu));
+            router.setQuery(menu.getQuery());
+            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
+            List<SysMenu> cMenus = menu.getChildren();
+            if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType()))
+            {
+                router.setAlwaysShow(true);
+                router.setRedirect("noRedirect");
+                router.setChildren(buildMenus(cMenus));
+            }
+            else if (isMenuFrame(menu))
+            {
+                router.setMeta(null);
+                List<RouterVo> childrenList = new ArrayList<RouterVo>();
+                RouterVo children = new RouterVo();
+                children.setPath(menu.getPath());
+                children.setComponent(menu.getComponent());
+                children.setName(getRouteName(menu.getRouteName(), menu.getPath()));
+                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
+                children.setQuery(menu.getQuery());
+                childrenList.add(children);
+                router.setChildren(childrenList);
+            }
+            else if (menu.getParentId().intValue() == 0 && isInnerLink(menu))
+            {
+                router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
+                router.setPath("/");
+                List<RouterVo> childrenList = new ArrayList<RouterVo>();
+                RouterVo children = new RouterVo();
+                String routerPath = innerLinkReplaceEach(menu.getPath());
+                children.setPath(routerPath);
+                children.setComponent(UserConstants.INNER_LINK);
+                children.setName(getRouteName(menu.getRouteName(), routerPath));
+                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
+                childrenList.add(children);
+                router.setChildren(childrenList);
+            }
+            routers.add(router);
+        }
+        return routers;
+    }
+
+    /**
+     * 构建前端所需要树结构
+     * 
+     * @param menus 菜单列表
+     * @return 树结构列表
+     */
+    @Override
+    public List<SysMenu> buildMenuTree(List<SysMenu> menus)
+    {
+        List<SysMenu> returnList = new ArrayList<SysMenu>();
+        List<Long> tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList());
+        for (Iterator<SysMenu> iterator = menus.iterator(); iterator.hasNext();)
+        {
+            SysMenu menu = (SysMenu) iterator.next();
+            // 如果是顶级节点, 遍历该父节点的所有子节点
+            if (!tempList.contains(menu.getParentId()))
+            {
+                recursionFn(menus, menu);
+                returnList.add(menu);
+            }
+        }
+        if (returnList.isEmpty())
+        {
+            returnList = menus;
+        }
+        return returnList;
+    }
+
+    /**
+     * 构建前端所需要下拉树结构
+     * 
+     * @param menus 菜单列表
+     * @return 下拉树结构列表
+     */
+    @Override
+    public List<TreeSelect> buildMenuTreeSelect(List<SysMenu> menus)
+    {
+        List<SysMenu> menuTrees = buildMenuTree(menus);
+        return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
+    }
+
+    /**
+     * 根据菜单ID查询信息
+     * 
+     * @param menuId 菜单ID
+     * @return 菜单信息
+     */
+    @Override
+    public SysMenu selectMenuById(Long menuId)
+    {
+        return menuMapper.selectMenuById(menuId);
+    }
+
+    /**
+     * 是否存在菜单子节点
+     * 
+     * @param menuId 菜单ID
+     * @return 结果
+     */
+    @Override
+    public boolean hasChildByMenuId(Long menuId)
+    {
+        int result = menuMapper.hasChildByMenuId(menuId);
+        return result > 0;
+    }
+
+    /**
+     * 查询菜单使用数量
+     * 
+     * @param menuId 菜单ID
+     * @return 结果
+     */
+    @Override
+    public boolean checkMenuExistRole(Long menuId)
+    {
+        int result = roleMenuMapper.checkMenuExistRole(menuId);
+        return result > 0;
+    }
+
+    /**
+     * 新增保存菜单信息
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    @Override
+    public int insertMenu(SysMenu menu)
+    {
+        return menuMapper.insertMenu(menu);
+    }
+
+    /**
+     * 修改保存菜单信息
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    @Override
+    public int updateMenu(SysMenu menu)
+    {
+        return menuMapper.updateMenu(menu);
+    }
+
+    /**
+     * 删除菜单管理信息
+     * 
+     * @param menuId 菜单ID
+     * @return 结果
+     */
+    @Override
+    public int deleteMenuById(Long menuId)
+    {
+        return menuMapper.deleteMenuById(menuId);
+    }
+
+    /**
+     * 校验菜单名称是否唯一
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkMenuNameUnique(SysMenu menu)
+    {
+        Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();
+        SysMenu info = menuMapper.checkMenuNameUnique(menu.getMenuName(), menu.getParentId());
+        if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 获取路由名称
+     * 
+     * @param menu 菜单信息
+     * @return 路由名称
+     */
+    public String getRouteName(SysMenu menu)
+    {
+        // 非外链并且是一级目录(类型为目录)
+        if (isMenuFrame(menu))
+        {
+            return StringUtils.EMPTY;
+        }
+        return getRouteName(menu.getRouteName(), menu.getPath());
+    }
+
+    /**
+     * 获取路由名称,如没有配置路由名称则取路由地址
+     * 
+     * @param routerName 路由名称
+     * @param path 路由地址
+     * @return 路由名称(驼峰格式)
+     */
+    public String getRouteName(String name, String path)
+    {
+        String routerName = StringUtils.isNotEmpty(name) ? name : path;
+        return StringUtils.capitalize(routerName);
+    }
+
+    /**
+     * 获取路由地址
+     * 
+     * @param menu 菜单信息
+     * @return 路由地址
+     */
+    public String getRouterPath(SysMenu menu)
+    {
+        String routerPath = menu.getPath();
+        // 内链打开外网方式
+        if (menu.getParentId().intValue() != 0 && isInnerLink(menu))
+        {
+            routerPath = innerLinkReplaceEach(routerPath);
+        }
+        // 非外链并且是一级目录(类型为目录)
+        if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())
+                && UserConstants.NO_FRAME.equals(menu.getIsFrame()))
+        {
+            routerPath = "/" + menu.getPath();
+        }
+        // 非外链并且是一级目录(类型为菜单)
+        else if (isMenuFrame(menu))
+        {
+            routerPath = "/";
+        }
+        return routerPath;
+    }
+
+    /**
+     * 获取组件信息
+     * 
+     * @param menu 菜单信息
+     * @return 组件信息
+     */
+    public String getComponent(SysMenu menu)
+    {
+        String component = UserConstants.LAYOUT;
+        if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu))
+        {
+            component = menu.getComponent();
+        }
+        else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu))
+        {
+            component = UserConstants.INNER_LINK;
+        }
+        else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu))
+        {
+            component = UserConstants.PARENT_VIEW;
+        }
+        return component;
+    }
+
+    /**
+     * 是否为菜单内部跳转
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    public boolean isMenuFrame(SysMenu menu)
+    {
+        return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType())
+                && menu.getIsFrame().equals(UserConstants.NO_FRAME);
+    }
+
+    /**
+     * 是否为内链组件
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    public boolean isInnerLink(SysMenu menu)
+    {
+        return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath());
+    }
+
+    /**
+     * 是否为parent_view组件
+     * 
+     * @param menu 菜单信息
+     * @return 结果
+     */
+    public boolean isParentView(SysMenu menu)
+    {
+        return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType());
+    }
+
+    /**
+     * 根据父节点的ID获取所有子节点
+     * 
+     * @param list 分类表
+     * @param parentId 传入的父节点ID
+     * @return String
+     */
+    public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId)
+    {
+        List<SysMenu> returnList = new ArrayList<SysMenu>();
+        for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)
+        {
+            SysMenu t = (SysMenu) iterator.next();
+            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
+            if (t.getParentId() == parentId)
+            {
+                recursionFn(list, t);
+                returnList.add(t);
+            }
+        }
+        return returnList;
+    }
+
+    /**
+     * 递归列表
+     * 
+     * @param list 分类表
+     * @param t 子节点
+     */
+    private void recursionFn(List<SysMenu> list, SysMenu t)
+    {
+        // 得到子节点列表
+        List<SysMenu> childList = getChildList(list, t);
+        t.setChildren(childList);
+        for (SysMenu tChild : childList)
+        {
+            if (hasChild(list, tChild))
+            {
+                recursionFn(list, tChild);
+            }
+        }
+    }
+
+    /**
+     * 得到子节点列表
+     */
+    private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t)
+    {
+        List<SysMenu> tlist = new ArrayList<SysMenu>();
+        Iterator<SysMenu> it = list.iterator();
+        while (it.hasNext())
+        {
+            SysMenu n = (SysMenu) it.next();
+            if (n.getParentId().longValue() == t.getMenuId().longValue())
+            {
+                tlist.add(n);
+            }
+        }
+        return tlist;
+    }
+
+    /**
+     * 判断是否有子节点
+     */
+    private boolean hasChild(List<SysMenu> list, SysMenu t)
+    {
+        return getChildList(list, t).size() > 0;
+    }
+
+    /**
+     * 内链域名特殊字符替换
+     * 
+     * @return 替换后的内链域名
+     */
+    public String innerLinkReplaceEach(String path)
+    {
+        return StringUtils.replaceEach(path, new String[] { Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":" },
+                new String[] { "", "", "", "/", "/" });
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java
new file mode 100644
index 0000000..765438b
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java
@@ -0,0 +1,92 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.domain.SysNotice;
+import com.ruoyi.system.mapper.SysNoticeMapper;
+import com.ruoyi.system.service.ISysNoticeService;
+
+/**
+ * 公告 服务层实现
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysNoticeServiceImpl implements ISysNoticeService
+{
+    @Autowired
+    private SysNoticeMapper noticeMapper;
+
+    /**
+     * 查询公告信息
+     * 
+     * @param noticeId 公告ID
+     * @return 公告信息
+     */
+    @Override
+    public SysNotice selectNoticeById(Long noticeId)
+    {
+        return noticeMapper.selectNoticeById(noticeId);
+    }
+
+    /**
+     * 查询公告列表
+     * 
+     * @param notice 公告信息
+     * @return 公告集合
+     */
+    @Override
+    public List<SysNotice> selectNoticeList(SysNotice notice)
+    {
+        return noticeMapper.selectNoticeList(notice);
+    }
+
+    /**
+     * 新增公告
+     * 
+     * @param notice 公告信息
+     * @return 结果
+     */
+    @Override
+    public int insertNotice(SysNotice notice)
+    {
+        return noticeMapper.insertNotice(notice);
+    }
+
+    /**
+     * 修改公告
+     * 
+     * @param notice 公告信息
+     * @return 结果
+     */
+    @Override
+    public int updateNotice(SysNotice notice)
+    {
+        return noticeMapper.updateNotice(notice);
+    }
+
+    /**
+     * 删除公告对象
+     * 
+     * @param noticeId 公告ID
+     * @return 结果
+     */
+    @Override
+    public int deleteNoticeById(Long noticeId)
+    {
+        return noticeMapper.deleteNoticeById(noticeId);
+    }
+
+    /**
+     * 批量删除公告信息
+     * 
+     * @param noticeIds 需要删除的公告ID
+     * @return 结果
+     */
+    @Override
+    public int deleteNoticeByIds(Long[] noticeIds)
+    {
+        return noticeMapper.deleteNoticeByIds(noticeIds);
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java
new file mode 100644
index 0000000..5489815
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java
@@ -0,0 +1,76 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.domain.SysOperLog;
+import com.ruoyi.system.mapper.SysOperLogMapper;
+import com.ruoyi.system.service.ISysOperLogService;
+
+/**
+ * 操作日志 服务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysOperLogServiceImpl implements ISysOperLogService
+{
+    @Autowired
+    private SysOperLogMapper operLogMapper;
+
+    /**
+     * 新增操作日志
+     * 
+     * @param operLog 操作日志对象
+     */
+    @Override
+    public void insertOperlog(SysOperLog operLog)
+    {
+        operLogMapper.insertOperlog(operLog);
+    }
+
+    /**
+     * 查询系统操作日志集合
+     * 
+     * @param operLog 操作日志对象
+     * @return 操作日志集合
+     */
+    @Override
+    public List<SysOperLog> selectOperLogList(SysOperLog operLog)
+    {
+        return operLogMapper.selectOperLogList(operLog);
+    }
+
+    /**
+     * 批量删除系统操作日志
+     * 
+     * @param operIds 需要删除的操作日志ID
+     * @return 结果
+     */
+    @Override
+    public int deleteOperLogByIds(Long[] operIds)
+    {
+        return operLogMapper.deleteOperLogByIds(operIds);
+    }
+
+    /**
+     * 查询操作日志详细
+     * 
+     * @param operId 操作ID
+     * @return 操作日志对象
+     */
+    @Override
+    public SysOperLog selectOperLogById(Long operId)
+    {
+        return operLogMapper.selectOperLogById(operId);
+    }
+
+    /**
+     * 清空操作日志
+     */
+    @Override
+    public void cleanOperLog()
+    {
+        operLogMapper.cleanOperLog();
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java
new file mode 100644
index 0000000..5e5fe06
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java
@@ -0,0 +1,178 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysPost;
+import com.ruoyi.system.mapper.SysPostMapper;
+import com.ruoyi.system.mapper.SysUserPostMapper;
+import com.ruoyi.system.service.ISysPostService;
+
+/**
+ * 岗位信息 服务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysPostServiceImpl implements ISysPostService
+{
+    @Autowired
+    private SysPostMapper postMapper;
+
+    @Autowired
+    private SysUserPostMapper userPostMapper;
+
+    /**
+     * 查询岗位信息集合
+     * 
+     * @param post 岗位信息
+     * @return 岗位信息集合
+     */
+    @Override
+    public List<SysPost> selectPostList(SysPost post)
+    {
+        return postMapper.selectPostList(post);
+    }
+
+    /**
+     * 查询所有岗位
+     * 
+     * @return 岗位列表
+     */
+    @Override
+    public List<SysPost> selectPostAll()
+    {
+        return postMapper.selectPostAll();
+    }
+
+    /**
+     * 通过岗位ID查询岗位信息
+     * 
+     * @param postId 岗位ID
+     * @return 角色对象信息
+     */
+    @Override
+    public SysPost selectPostById(Long postId)
+    {
+        return postMapper.selectPostById(postId);
+    }
+
+    /**
+     * 根据用户ID获取岗位选择框列表
+     * 
+     * @param userId 用户ID
+     * @return 选中岗位ID列表
+     */
+    @Override
+    public List<Long> selectPostListByUserId(Long userId)
+    {
+        return postMapper.selectPostListByUserId(userId);
+    }
+
+    /**
+     * 校验岗位名称是否唯一
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkPostNameUnique(SysPost post)
+    {
+        Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId();
+        SysPost info = postMapper.checkPostNameUnique(post.getPostName());
+        if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 校验岗位编码是否唯一
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkPostCodeUnique(SysPost post)
+    {
+        Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId();
+        SysPost info = postMapper.checkPostCodeUnique(post.getPostCode());
+        if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 通过岗位ID查询岗位使用数量
+     * 
+     * @param postId 岗位ID
+     * @return 结果
+     */
+    @Override
+    public int countUserPostById(Long postId)
+    {
+        return userPostMapper.countUserPostById(postId);
+    }
+
+    /**
+     * 删除岗位信息
+     * 
+     * @param postId 岗位ID
+     * @return 结果
+     */
+    @Override
+    public int deletePostById(Long postId)
+    {
+        return postMapper.deletePostById(postId);
+    }
+
+    /**
+     * 批量删除岗位信息
+     * 
+     * @param postIds 需要删除的岗位ID
+     * @return 结果
+     */
+    @Override
+    public int deletePostByIds(Long[] postIds)
+    {
+        for (Long postId : postIds)
+        {
+            SysPost post = selectPostById(postId);
+            if (countUserPostById(postId) > 0)
+            {
+                throw new ServiceException(String.format("%1$s已分配,不能删除", post.getPostName()));
+            }
+        }
+        return postMapper.deletePostByIds(postIds);
+    }
+
+    /**
+     * 新增保存岗位信息
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    @Override
+    public int insertPost(SysPost post)
+    {
+        return postMapper.insertPost(post);
+    }
+
+    /**
+     * 修改保存岗位信息
+     * 
+     * @param post 岗位信息
+     * @return 结果
+     */
+    @Override
+    public int updatePost(SysPost post)
+    {
+        return postMapper.updatePost(post);
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
new file mode 100644
index 0000000..e432bb1
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
@@ -0,0 +1,427 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.ruoyi.common.annotation.DataScope;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.system.domain.SysRoleDept;
+import com.ruoyi.system.domain.SysRoleMenu;
+import com.ruoyi.system.domain.SysUserRole;
+import com.ruoyi.system.mapper.SysRoleDeptMapper;
+import com.ruoyi.system.mapper.SysRoleMapper;
+import com.ruoyi.system.mapper.SysRoleMenuMapper;
+import com.ruoyi.system.mapper.SysUserRoleMapper;
+import com.ruoyi.system.service.ISysRoleService;
+
+/**
+ * 角色 业务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysRoleServiceImpl implements ISysRoleService
+{
+    @Autowired
+    private SysRoleMapper roleMapper;
+
+    @Autowired
+    private SysRoleMenuMapper roleMenuMapper;
+
+    @Autowired
+    private SysUserRoleMapper userRoleMapper;
+
+    @Autowired
+    private SysRoleDeptMapper roleDeptMapper;
+
+    /**
+     * 根据条件分页查询角色数据
+     * 
+     * @param role 角色信息
+     * @return 角色数据集合信息
+     */
+    @Override
+    @DataScope(deptAlias = "d")
+    public List<SysRole> selectRoleList(SysRole role)
+    {
+        return roleMapper.selectRoleList(role);
+    }
+
+    /**
+     * 根据用户ID查询角色
+     * 
+     * @param userId 用户ID
+     * @return 角色列表
+     */
+    @Override
+    public List<SysRole> selectRolesByUserId(Long userId)
+    {
+        List<SysRole> userRoles = roleMapper.selectRolePermissionByUserId(userId);
+        List<SysRole> roles = selectRoleAll();
+        for (SysRole role : roles)
+        {
+            for (SysRole userRole : userRoles)
+            {
+                if (role.getRoleId().longValue() == userRole.getRoleId().longValue())
+                {
+                    role.setFlag(true);
+                    break;
+                }
+            }
+        }
+        return roles;
+    }
+
+    /**
+     * 根据用户ID查询权限
+     * 
+     * @param userId 用户ID
+     * @return 权限列表
+     */
+    @Override
+    public Set<String> selectRolePermissionByUserId(Long userId)
+    {
+        List<SysRole> perms = roleMapper.selectRolePermissionByUserId(userId);
+        Set<String> permsSet = new HashSet<>();
+        for (SysRole perm : perms)
+        {
+            if (StringUtils.isNotNull(perm))
+            {
+                permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
+            }
+        }
+        return permsSet;
+    }
+
+    /**
+     * 查询所有角色
+     * 
+     * @return 角色列表
+     */
+    @Override
+    public List<SysRole> selectRoleAll()
+    {
+        return SpringUtils.getAopProxy(this).selectRoleList(new SysRole());
+    }
+
+    /**
+     * 根据用户ID获取角色选择框列表
+     * 
+     * @param userId 用户ID
+     * @return 选中角色ID列表
+     */
+    @Override
+    public List<Long> selectRoleListByUserId(Long userId)
+    {
+        return roleMapper.selectRoleListByUserId(userId);
+    }
+
+    /**
+     * 通过角色ID查询角色
+     * 
+     * @param roleId 角色ID
+     * @return 角色对象信息
+     */
+    @Override
+    public SysRole selectRoleById(Long roleId)
+    {
+        return roleMapper.selectRoleById(roleId);
+    }
+
+    /**
+     * 校验角色名称是否唯一
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkRoleNameUnique(SysRole role)
+    {
+        Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId();
+        SysRole info = roleMapper.checkRoleNameUnique(role.getRoleName());
+        if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 校验角色权限是否唯一
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkRoleKeyUnique(SysRole role)
+    {
+        Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId();
+        SysRole info = roleMapper.checkRoleKeyUnique(role.getRoleKey());
+        if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 校验角色是否允许操作
+     * 
+     * @param role 角色信息
+     */
+    @Override
+    public void checkRoleAllowed(SysRole role)
+    {
+        if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin())
+        {
+            throw new ServiceException("不允许操作超级管理员角色");
+        }
+    }
+
+    /**
+     * 校验角色是否有数据权限
+     * 
+     * @param roleIds 角色id
+     */
+    @Override
+    public void checkRoleDataScope(Long... roleIds)
+    {
+        if (!SysUser.isAdmin(SecurityUtils.getUserId()))
+        {
+            for (Long roleId : roleIds)
+            {
+                SysRole role = new SysRole();
+                role.setRoleId(roleId);
+                List<SysRole> roles = SpringUtils.getAopProxy(this).selectRoleList(role);
+                if (StringUtils.isEmpty(roles))
+                {
+                    throw new ServiceException("没有权限访问角色数据!");
+                }
+            }
+        }
+    }
+
+    /**
+     * 通过角色ID查询角色使用数量
+     * 
+     * @param roleId 角色ID
+     * @return 结果
+     */
+    @Override
+    public int countUserRoleByRoleId(Long roleId)
+    {
+        return userRoleMapper.countUserRoleByRoleId(roleId);
+    }
+
+    /**
+     * 新增保存角色信息
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int insertRole(SysRole role)
+    {
+        // 新增角色信息
+        roleMapper.insertRole(role);
+        return insertRoleMenu(role);
+    }
+
+    /**
+     * 修改保存角色信息
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int updateRole(SysRole role)
+    {
+        // 修改角色信息
+        roleMapper.updateRole(role);
+        // 删除角色与菜单关联
+        roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId());
+        return insertRoleMenu(role);
+    }
+
+    /**
+     * 修改角色状态
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    @Override
+    public int updateRoleStatus(SysRole role)
+    {
+        return roleMapper.updateRole(role);
+    }
+
+    /**
+     * 修改数据权限信息
+     * 
+     * @param role 角色信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int authDataScope(SysRole role)
+    {
+        // 修改角色信息
+        roleMapper.updateRole(role);
+        // 删除角色与部门关联
+        roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId());
+        // 新增角色和部门信息(数据权限)
+        return insertRoleDept(role);
+    }
+
+    /**
+     * 新增角色菜单信息
+     * 
+     * @param role 角色对象
+     */
+    public int insertRoleMenu(SysRole role)
+    {
+        int rows = 1;
+        // 新增用户与角色管理
+        List<SysRoleMenu> list = new ArrayList<SysRoleMenu>();
+        for (Long menuId : role.getMenuIds())
+        {
+            SysRoleMenu rm = new SysRoleMenu();
+            rm.setRoleId(role.getRoleId());
+            rm.setMenuId(menuId);
+            list.add(rm);
+        }
+        if (list.size() > 0)
+        {
+            rows = roleMenuMapper.batchRoleMenu(list);
+        }
+        return rows;
+    }
+
+    /**
+     * 新增角色部门信息(数据权限)
+     *
+     * @param role 角色对象
+     */
+    public int insertRoleDept(SysRole role)
+    {
+        int rows = 1;
+        // 新增角色与部门(数据权限)管理
+        List<SysRoleDept> list = new ArrayList<SysRoleDept>();
+        for (Long deptId : role.getDeptIds())
+        {
+            SysRoleDept rd = new SysRoleDept();
+            rd.setRoleId(role.getRoleId());
+            rd.setDeptId(deptId);
+            list.add(rd);
+        }
+        if (list.size() > 0)
+        {
+            rows = roleDeptMapper.batchRoleDept(list);
+        }
+        return rows;
+    }
+
+    /**
+     * 通过角色ID删除角色
+     * 
+     * @param roleId 角色ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int deleteRoleById(Long roleId)
+    {
+        // 删除角色与菜单关联
+        roleMenuMapper.deleteRoleMenuByRoleId(roleId);
+        // 删除角色与部门关联
+        roleDeptMapper.deleteRoleDeptByRoleId(roleId);
+        return roleMapper.deleteRoleById(roleId);
+    }
+
+    /**
+     * 批量删除角色信息
+     * 
+     * @param roleIds 需要删除的角色ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int deleteRoleByIds(Long[] roleIds)
+    {
+        for (Long roleId : roleIds)
+        {
+            checkRoleAllowed(new SysRole(roleId));
+            checkRoleDataScope(roleId);
+            SysRole role = selectRoleById(roleId);
+            if (countUserRoleByRoleId(roleId) > 0)
+            {
+                throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName()));
+            }
+        }
+        // 删除角色与菜单关联
+        roleMenuMapper.deleteRoleMenu(roleIds);
+        // 删除角色与部门关联
+        roleDeptMapper.deleteRoleDept(roleIds);
+        return roleMapper.deleteRoleByIds(roleIds);
+    }
+
+    /**
+     * 取消授权用户角色
+     * 
+     * @param userRole 用户和角色关联信息
+     * @return 结果
+     */
+    @Override
+    public int deleteAuthUser(SysUserRole userRole)
+    {
+        return userRoleMapper.deleteUserRoleInfo(userRole);
+    }
+
+    /**
+     * 批量取消授权用户角色
+     * 
+     * @param roleId 角色ID
+     * @param userIds 需要取消授权的用户数据ID
+     * @return 结果
+     */
+    @Override
+    public int deleteAuthUsers(Long roleId, Long[] userIds)
+    {
+        return userRoleMapper.deleteUserRoleInfos(roleId, userIds);
+    }
+
+    /**
+     * 批量选择授权用户角色
+     * 
+     * @param roleId 角色ID
+     * @param userIds 需要授权的用户数据ID
+     * @return 结果
+     */
+    @Override
+    public int insertAuthUsers(Long roleId, Long[] userIds)
+    {
+        // 新增用户与角色管理
+        List<SysUserRole> list = new ArrayList<SysUserRole>();
+        for (Long userId : userIds)
+        {
+            SysUserRole ur = new SysUserRole();
+            ur.setUserId(userId);
+            ur.setRoleId(roleId);
+            list.add(ur);
+        }
+        return userRoleMapper.batchUserRole(list);
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java
new file mode 100644
index 0000000..f80a877
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java
@@ -0,0 +1,96 @@
+package com.ruoyi.system.service.impl;
+
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.SysUserOnline;
+import com.ruoyi.system.service.ISysUserOnlineService;
+
+/**
+ * 在线用户 服务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysUserOnlineServiceImpl implements ISysUserOnlineService
+{
+    /**
+     * 通过登录地址查询信息
+     * 
+     * @param ipaddr 登录地址
+     * @param user 用户信息
+     * @return 在线用户信息
+     */
+    @Override
+    public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user)
+    {
+        if (StringUtils.equals(ipaddr, user.getIpaddr()))
+        {
+            return loginUserToUserOnline(user);
+        }
+        return null;
+    }
+
+    /**
+     * 通过用户名称查询信息
+     * 
+     * @param userName 用户名称
+     * @param user 用户信息
+     * @return 在线用户信息
+     */
+    @Override
+    public SysUserOnline selectOnlineByUserName(String userName, LoginUser user)
+    {
+        if (StringUtils.equals(userName, user.getUsername()))
+        {
+            return loginUserToUserOnline(user);
+        }
+        return null;
+    }
+
+    /**
+     * 通过登录地址/用户名称查询信息
+     * 
+     * @param ipaddr 登录地址
+     * @param userName 用户名称
+     * @param user 用户信息
+     * @return 在线用户信息
+     */
+    @Override
+    public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user)
+    {
+        if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername()))
+        {
+            return loginUserToUserOnline(user);
+        }
+        return null;
+    }
+
+    /**
+     * 设置在线用户信息
+     * 
+     * @param user 用户信息
+     * @return 在线用户
+     */
+    @Override
+    public SysUserOnline loginUserToUserOnline(LoginUser user)
+    {
+        if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser()))
+        {
+            return null;
+        }
+        SysUserOnline sysUserOnline = new SysUserOnline();
+        sysUserOnline.setTokenId(user.getToken());
+        sysUserOnline.setUserName(user.getUsername());
+        sysUserOnline.setIpaddr(user.getIpaddr());
+        sysUserOnline.setLoginLocation(user.getLoginLocation());
+        sysUserOnline.setBrowser(user.getBrowser());
+        sysUserOnline.setOs(user.getOs());
+        sysUserOnline.setLoginTime(user.getLoginTime());
+        if (StringUtils.isNotNull(user.getUser().getDept()))
+        {
+            sysUserOnline.setDeptName(user.getUser().getDept().getDeptName());
+        }
+        return sysUserOnline;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
new file mode 100644
index 0000000..82c303a
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -0,0 +1,550 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import jakarta.validation.Validator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import com.ruoyi.common.annotation.DataScope;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.bean.BeanValidators;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.system.domain.SysPost;
+import com.ruoyi.system.domain.SysUserPost;
+import com.ruoyi.system.domain.SysUserRole;
+import com.ruoyi.system.mapper.SysPostMapper;
+import com.ruoyi.system.mapper.SysRoleMapper;
+import com.ruoyi.system.mapper.SysUserMapper;
+import com.ruoyi.system.mapper.SysUserPostMapper;
+import com.ruoyi.system.mapper.SysUserRoleMapper;
+import com.ruoyi.system.service.ISysConfigService;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 用户 业务层处理
+ * 
+ * @author ruoyi
+ */
+@Service
+public class SysUserServiceImpl implements ISysUserService
+{
+    private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);
+
+    @Autowired
+    private SysUserMapper userMapper;
+
+    @Autowired
+    private SysRoleMapper roleMapper;
+
+    @Autowired
+    private SysPostMapper postMapper;
+
+    @Autowired
+    private SysUserRoleMapper userRoleMapper;
+
+    @Autowired
+    private SysUserPostMapper userPostMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private ISysDeptService deptService;
+
+    @Autowired
+    protected Validator validator;
+
+    /**
+     * 根据条件分页查询用户列表
+     * 
+     * @param user 用户信息
+     * @return 用户信息集合信息
+     */
+    @Override
+    @DataScope(deptAlias = "d", userAlias = "u")
+    public List<SysUser> selectUserList(SysUser user)
+    {
+        return userMapper.selectUserList(user);
+    }
+
+    /**
+     * 根据条件分页查询已分配用户角色列表
+     * 
+     * @param user 用户信息
+     * @return 用户信息集合信息
+     */
+    @Override
+    @DataScope(deptAlias = "d", userAlias = "u")
+    public List<SysUser> selectAllocatedList(SysUser user)
+    {
+        return userMapper.selectAllocatedList(user);
+    }
+
+    /**
+     * 根据条件分页查询未分配用户角色列表
+     * 
+     * @param user 用户信息
+     * @return 用户信息集合信息
+     */
+    @Override
+    @DataScope(deptAlias = "d", userAlias = "u")
+    public List<SysUser> selectUnallocatedList(SysUser user)
+    {
+        return userMapper.selectUnallocatedList(user);
+    }
+
+    /**
+     * 通过用户名查询用户
+     * 
+     * @param userName 用户名
+     * @return 用户对象信息
+     */
+    @Override
+    public SysUser selectUserByUserName(String userName)
+    {
+        return userMapper.selectUserByUserName(userName);
+    }
+
+    /**
+     * 通过用户ID查询用户
+     * 
+     * @param userId 用户ID
+     * @return 用户对象信息
+     */
+    @Override
+    public SysUser selectUserById(Long userId)
+    {
+        return userMapper.selectUserById(userId);
+    }
+
+    /**
+     * 查询用户所属角色组
+     * 
+     * @param userName 用户名
+     * @return 结果
+     */
+    @Override
+    public String selectUserRoleGroup(String userName)
+    {
+        List<SysRole> list = roleMapper.selectRolesByUserName(userName);
+        if (CollectionUtils.isEmpty(list))
+        {
+            return StringUtils.EMPTY;
+        }
+        return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(","));
+    }
+
+    /**
+     * 查询用户所属岗位组
+     * 
+     * @param userName 用户名
+     * @return 结果
+     */
+    @Override
+    public String selectUserPostGroup(String userName)
+    {
+        List<SysPost> list = postMapper.selectPostsByUserName(userName);
+        if (CollectionUtils.isEmpty(list))
+        {
+            return StringUtils.EMPTY;
+        }
+        return list.stream().map(SysPost::getPostName).collect(Collectors.joining(","));
+    }
+
+    /**
+     * 校验用户名称是否唯一
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    @Override
+    public boolean checkUserNameUnique(SysUser user)
+    {
+        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
+        SysUser info = userMapper.checkUserNameUnique(user.getUserName());
+        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 校验手机号码是否唯一
+     *
+     * @param user 用户信息
+     * @return
+     */
+    @Override
+    public boolean checkPhoneUnique(SysUser user)
+    {
+        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
+        SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber());
+        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 校验email是否唯一
+     *
+     * @param user 用户信息
+     * @return
+     */
+    @Override
+    public boolean checkEmailUnique(SysUser user)
+    {
+        Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
+        SysUser info = userMapper.checkEmailUnique(user.getEmail());
+        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
+        {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    /**
+     * 校验用户是否允许操作
+     * 
+     * @param user 用户信息
+     */
+    @Override
+    public void checkUserAllowed(SysUser user)
+    {
+        if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin())
+        {
+            throw new ServiceException("不允许操作超级管理员用户");
+        }
+    }
+
+    /**
+     * 校验用户是否有数据权限
+     * 
+     * @param userId 用户id
+     */
+    @Override
+    public void checkUserDataScope(Long userId)
+    {
+        if (!SysUser.isAdmin(SecurityUtils.getUserId()))
+        {
+            SysUser user = new SysUser();
+            user.setUserId(userId);
+            List<SysUser> users = SpringUtils.getAopProxy(this).selectUserList(user);
+            if (StringUtils.isEmpty(users))
+            {
+                throw new ServiceException("没有权限访问用户数据!");
+            }
+        }
+    }
+
+    /**
+     * 新增保存用户信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int insertUser(SysUser user)
+    {
+        // 新增用户信息
+        int rows = userMapper.insertUser(user);
+        // 新增用户岗位关联
+        insertUserPost(user);
+        // 新增用户与角色管理
+        insertUserRole(user);
+        return rows;
+    }
+
+    /**
+     * 注册用户信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    @Override
+    public boolean registerUser(SysUser user)
+    {
+        return userMapper.insertUser(user) > 0;
+    }
+
+    /**
+     * 修改保存用户信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int updateUser(SysUser user)
+    {
+        Long userId = user.getUserId();
+        // 删除用户与角色关联
+        userRoleMapper.deleteUserRoleByUserId(userId);
+        // 新增用户与角色管理
+        insertUserRole(user);
+        // 删除用户与岗位关联
+        userPostMapper.deleteUserPostByUserId(userId);
+        // 新增用户与岗位管理
+        insertUserPost(user);
+        return userMapper.updateUser(user);
+    }
+
+    /**
+     * 用户授权角色
+     * 
+     * @param userId 用户ID
+     * @param roleIds 角色组
+     */
+    @Override
+    @Transactional
+    public void insertUserAuth(Long userId, Long[] roleIds)
+    {
+        userRoleMapper.deleteUserRoleByUserId(userId);
+        insertUserRole(userId, roleIds);
+    }
+
+    /**
+     * 修改用户状态
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    @Override
+    public int updateUserStatus(SysUser user)
+    {
+        return userMapper.updateUser(user);
+    }
+
+    /**
+     * 修改用户基本信息
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    @Override
+    public int updateUserProfile(SysUser user)
+    {
+        return userMapper.updateUser(user);
+    }
+
+    /**
+     * 修改用户头像
+     * 
+     * @param userName 用户名
+     * @param avatar 头像地址
+     * @return 结果
+     */
+    @Override
+    public boolean updateUserAvatar(String userName, String avatar)
+    {
+        return userMapper.updateUserAvatar(userName, avatar) > 0;
+    }
+
+    /**
+     * 重置用户密码
+     * 
+     * @param user 用户信息
+     * @return 结果
+     */
+    @Override
+    public int resetPwd(SysUser user)
+    {
+        return userMapper.updateUser(user);
+    }
+
+    /**
+     * 重置用户密码
+     * 
+     * @param userName 用户名
+     * @param password 密码
+     * @return 结果
+     */
+    @Override
+    public int resetUserPwd(String userName, String password)
+    {
+        return userMapper.resetUserPwd(userName, password);
+    }
+
+    /**
+     * 新增用户角色信息
+     * 
+     * @param user 用户对象
+     */
+    public void insertUserRole(SysUser user)
+    {
+        this.insertUserRole(user.getUserId(), user.getRoleIds());
+    }
+
+    /**
+     * 新增用户岗位信息
+     * 
+     * @param user 用户对象
+     */
+    public void insertUserPost(SysUser user)
+    {
+        Long[] posts = user.getPostIds();
+        if (StringUtils.isNotEmpty(posts))
+        {
+            // 新增用户与岗位管理
+            List<SysUserPost> list = new ArrayList<SysUserPost>(posts.length);
+            for (Long postId : posts)
+            {
+                SysUserPost up = new SysUserPost();
+                up.setUserId(user.getUserId());
+                up.setPostId(postId);
+                list.add(up);
+            }
+            userPostMapper.batchUserPost(list);
+        }
+    }
+
+    /**
+     * 新增用户角色信息
+     * 
+     * @param userId 用户ID
+     * @param roleIds 角色组
+     */
+    public void insertUserRole(Long userId, Long[] roleIds)
+    {
+        if (StringUtils.isNotEmpty(roleIds))
+        {
+            // 新增用户与角色管理
+            List<SysUserRole> list = new ArrayList<SysUserRole>(roleIds.length);
+            for (Long roleId : roleIds)
+            {
+                SysUserRole ur = new SysUserRole();
+                ur.setUserId(userId);
+                ur.setRoleId(roleId);
+                list.add(ur);
+            }
+            userRoleMapper.batchUserRole(list);
+        }
+    }
+
+    /**
+     * 通过用户ID删除用户
+     * 
+     * @param userId 用户ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int deleteUserById(Long userId)
+    {
+        // 删除用户与角色关联
+        userRoleMapper.deleteUserRoleByUserId(userId);
+        // 删除用户与岗位表
+        userPostMapper.deleteUserPostByUserId(userId);
+        return userMapper.deleteUserById(userId);
+    }
+
+    /**
+     * 批量删除用户信息
+     * 
+     * @param userIds 需要删除的用户ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int deleteUserByIds(Long[] userIds)
+    {
+        for (Long userId : userIds)
+        {
+            checkUserAllowed(new SysUser(userId));
+            checkUserDataScope(userId);
+        }
+        // 删除用户与角色关联
+        userRoleMapper.deleteUserRole(userIds);
+        // 删除用户与岗位关联
+        userPostMapper.deleteUserPost(userIds);
+        return userMapper.deleteUserByIds(userIds);
+    }
+
+    /**
+     * 导入用户数据
+     * 
+     * @param userList 用户数据列表
+     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
+     * @param operName 操作用户
+     * @return 结果
+     */
+    @Override
+    public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName)
+    {
+        if (StringUtils.isNull(userList) || userList.size() == 0)
+        {
+            throw new ServiceException("导入用户数据不能为空!");
+        }
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        for (SysUser user : userList)
+        {
+            try
+            {
+                // 验证是否存在这个用户
+                SysUser u = userMapper.selectUserByUserName(user.getUserName());
+                if (StringUtils.isNull(u))
+                {
+                    BeanValidators.validateWithException(validator, user);
+                    deptService.checkDeptDataScope(user.getDeptId());
+                    String password = configService.selectConfigByKey("sys.user.initPassword");
+                    user.setPassword(SecurityUtils.encryptPassword(password));
+                    user.setCreateBy(operName);
+                    userMapper.insertUser(user);
+                    successNum++;
+                    successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功");
+                }
+                else if (isUpdateSupport)
+                {
+                    BeanValidators.validateWithException(validator, user);
+                    checkUserAllowed(u);
+                    checkUserDataScope(u.getUserId());
+                    deptService.checkDeptDataScope(user.getDeptId());
+                    user.setUserId(u.getUserId());
+                    user.setUpdateBy(operName);
+                    userMapper.updateUser(user);
+                    successNum++;
+                    successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功");
+                }
+                else
+                {
+                    failureNum++;
+                    failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在");
+                }
+            }
+            catch (Exception e)
+            {
+                failureNum++;
+                String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:";
+                failureMsg.append(msg + e.getMessage());
+                log.error(msg, e);
+            }
+        }
+        if (failureNum > 0)
+        {
+            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+            throw new ServiceException(failureMsg.toString());
+        }
+        else
+        {
+            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+        }
+        return successMsg.toString();
+    }
+}
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml
new file mode 100644
index 0000000..a5ff114
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysConfigMapper">
+    
+    <resultMap type="SysConfig" id="SysConfigResult">
+    	<id     property="configId"      column="config_id"      />
+        <result property="configName"    column="config_name"    />
+        <result property="configKey"     column="config_key"     />
+        <result property="configValue"   column="config_value"   />
+        <result property="configType"    column="config_type"    />
+        <result property="createBy"      column="create_by"      />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"      column="update_by"      />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+    
+    <sql id="selectConfigVo">
+        select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark 
+		from sys_config
+    </sql>
+    
+    <!-- 查询条件 -->
+	<sql id="sqlwhereSearch">
+		<where>
+			<if test="configId !=null">
+				and config_id = #{configId}
+			</if>
+			<if test="configKey !=null and configKey != ''">
+				and config_key = #{configKey}
+			</if>
+		</where>
+	</sql>
+    
+    <select id="selectConfig" parameterType="SysConfig" resultMap="SysConfigResult">
+        <include refid="selectConfigVo"/>
+        <include refid="sqlwhereSearch"/>
+    </select>
+    
+    <select id="selectConfigList" parameterType="SysConfig" resultMap="SysConfigResult">
+        <include refid="selectConfigVo"/>
+        <where>
+			<if test="configName != null and configName != ''">
+				AND config_name like concat('%', #{configName}, '%')
+			</if>
+			<if test="configType != null and configType != ''">
+				AND config_type = #{configType}
+			</if>
+			<if test="configKey != null and configKey != ''">
+				AND config_key like concat('%', #{configKey}, '%')
+			</if>
+			<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+				and date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
+			</if>
+			<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+				and date_format(create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
+			</if>
+		</where>
+    </select>
+    
+    <select id="selectConfigById" parameterType="Long" resultMap="SysConfigResult">
+        <include refid="selectConfigVo"/>
+        where config_id = #{configId}
+    </select>
+	
+    <select id="checkConfigKeyUnique" parameterType="String" resultMap="SysConfigResult">
+        <include refid="selectConfigVo"/>
+        where config_key = #{configKey} limit 1
+    </select>
+    
+    <insert id="insertConfig" parameterType="SysConfig">
+        insert into sys_config (
+			<if test="configName != null and configName != '' ">config_name,</if>
+			<if test="configKey != null and configKey != '' ">config_key,</if>
+			<if test="configValue != null and configValue != '' ">config_value,</if>
+			<if test="configType != null and configType != '' ">config_type,</if>
+			<if test="createBy != null and createBy != ''">create_by,</if>
+			<if test="remark != null and remark != ''">remark,</if>
+ 			create_time
+        )values(
+			<if test="configName != null and configName != ''">#{configName},</if>
+			<if test="configKey != null and configKey != ''">#{configKey},</if>
+			<if test="configValue != null and configValue != ''">#{configValue},</if>
+			<if test="configType != null and configType != ''">#{configType},</if>
+			<if test="createBy != null and createBy != ''">#{createBy},</if>
+			<if test="remark != null and remark != ''">#{remark},</if>
+ 			sysdate()
+		)
+    </insert>
+	 
+    <update id="updateConfig" parameterType="SysConfig">
+        update sys_config 
+        <set>
+            <if test="configName != null and configName != ''">config_name = #{configName},</if>
+            <if test="configKey != null and configKey != ''">config_key = #{configKey},</if>
+            <if test="configValue != null and configValue != ''">config_value = #{configValue},</if>
+            <if test="configType != null and configType != ''">config_type = #{configType},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            <if test="remark != null">remark = #{remark},</if>
+ 			update_time = sysdate()
+        </set>
+        where config_id = #{configId}
+    </update>
+	
+    <delete id="deleteConfigById" parameterType="Long">
+        delete from sys_config where config_id = #{configId}
+    </delete>
+    
+    <delete id="deleteConfigByIds" parameterType="Long">
+        delete from sys_config where config_id in 
+        <foreach item="configId" collection="array" open="(" separator="," close=")">
+        	#{configId}
+        </foreach>
+    </delete>
+    
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
new file mode 100644
index 0000000..cf439f6
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysDeptMapper">
+
+	<resultMap type="SysDept" id="SysDeptResult">
+		<id     property="deptId"     column="dept_id"     />
+		<result property="parentId"   column="parent_id"   />
+		<result property="ancestors"  column="ancestors"   />
+		<result property="deptName"   column="dept_name"   />
+		<result property="orderNum"   column="order_num"   />
+		<result property="leader"     column="leader"      />
+		<result property="phone"      column="phone"       />
+		<result property="email"      column="email"       />
+		<result property="status"     column="status"      />
+		<result property="delFlag"    column="del_flag"    />
+		<result property="parentName" column="parent_name" />
+		<result property="createBy"   column="create_by"   />
+		<result property="createTime" column="create_time" />
+		<result property="updateBy"   column="update_by"   />
+		<result property="updateTime" column="update_time" />
+	</resultMap>
+	
+	<sql id="selectDeptVo">
+        select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time 
+        from sys_dept d
+    </sql>
+    
+	<select id="selectDeptList" parameterType="SysDept" resultMap="SysDeptResult">
+        <include refid="selectDeptVo"/>
+        where d.del_flag = '0'
+		<if test="deptId != null and deptId != 0">
+			AND dept_id = #{deptId}
+		</if>
+        <if test="parentId != null and parentId != 0">
+			AND parent_id = #{parentId}
+		</if>
+		<if test="deptName != null and deptName != ''">
+			AND dept_name like concat('%', #{deptName}, '%')
+		</if>
+		<if test="status != null and status != ''">
+			AND status = #{status}
+		</if>
+		<!-- 数据范围过滤 -->
+		${params.dataScope}
+		order by d.parent_id, d.order_num
+    </select>
+    
+    <select id="selectDeptListByRoleId" resultType="Long">
+		select d.dept_id
+		from sys_dept d
+            left join sys_role_dept rd on d.dept_id = rd.dept_id
+        where rd.role_id = #{roleId}
+            <if test="deptCheckStrictly">
+              and d.dept_id not in (select d.parent_id from sys_dept d inner join sys_role_dept rd on d.dept_id = rd.dept_id and rd.role_id = #{roleId})
+            </if>
+		order by d.parent_id, d.order_num
+	</select>
+    
+    <select id="selectDeptById" parameterType="Long" resultMap="SysDeptResult">
+		select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status,
+			(select dept_name from sys_dept where dept_id = d.parent_id) parent_name
+		from sys_dept d
+		where d.dept_id = #{deptId}
+	</select>
+    
+    <select id="checkDeptExistUser" parameterType="Long" resultType="int">
+		select count(1) from sys_user where dept_id = #{deptId} and del_flag = '0'
+	</select>
+	
+	<select id="hasChildByDeptId" parameterType="Long" resultType="int">
+		select count(1) from sys_dept
+		where del_flag = '0' and parent_id = #{deptId} limit 1
+	</select>
+	
+	<select id="selectChildrenDeptById" parameterType="Long" resultMap="SysDeptResult">
+		select * from sys_dept where find_in_set(#{deptId}, ancestors)
+	</select>
+	
+	<select id="selectNormalChildrenDeptById" parameterType="Long" resultType="int">
+		select count(*) from sys_dept where status = 0 and del_flag = '0' and find_in_set(#{deptId}, ancestors)
+	</select>
+	
+	<select id="checkDeptNameUnique" resultMap="SysDeptResult">
+	    <include refid="selectDeptVo"/>
+		where dept_name=#{deptName} and parent_id = #{parentId} and del_flag = '0' limit 1
+	</select>
+    
+    <insert id="insertDept" parameterType="SysDept">
+ 		insert into sys_dept(
+ 			<if test="deptId != null and deptId != 0">dept_id,</if>
+ 			<if test="parentId != null and parentId != 0">parent_id,</if>
+ 			<if test="deptName != null and deptName != ''">dept_name,</if>
+ 			<if test="ancestors != null and ancestors != ''">ancestors,</if>
+ 			<if test="orderNum != null">order_num,</if>
+ 			<if test="leader != null and leader != ''">leader,</if>
+ 			<if test="phone != null and phone != ''">phone,</if>
+ 			<if test="email != null and email != ''">email,</if>
+ 			<if test="status != null">status,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			create_time
+ 		)values(
+ 			<if test="deptId != null and deptId != 0">#{deptId},</if>
+ 			<if test="parentId != null and parentId != 0">#{parentId},</if>
+ 			<if test="deptName != null and deptName != ''">#{deptName},</if>
+ 			<if test="ancestors != null and ancestors != ''">#{ancestors},</if>
+ 			<if test="orderNum != null">#{orderNum},</if>
+ 			<if test="leader != null and leader != ''">#{leader},</if>
+ 			<if test="phone != null and phone != ''">#{phone},</if>
+ 			<if test="email != null and email != ''">#{email},</if>
+ 			<if test="status != null">#{status},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			sysdate()
+ 		)
+	</insert>
+	
+	<update id="updateDept" parameterType="SysDept">
+ 		update sys_dept
+ 		<set>
+ 			<if test="parentId != null and parentId != 0">parent_id = #{parentId},</if>
+ 			<if test="deptName != null and deptName != ''">dept_name = #{deptName},</if>
+ 			<if test="ancestors != null and ancestors != ''">ancestors = #{ancestors},</if>
+ 			<if test="orderNum != null">order_num = #{orderNum},</if>
+ 			<if test="leader != null">leader = #{leader},</if>
+ 			<if test="phone != null">phone = #{phone},</if>
+ 			<if test="email != null">email = #{email},</if>
+ 			<if test="status != null and status != ''">status = #{status},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where dept_id = #{deptId}
+	</update>
+	
+	<update id="updateDeptChildren" parameterType="java.util.List">
+	    update sys_dept set ancestors =
+	    <foreach collection="depts" item="item" index="index"
+	        separator=" " open="case dept_id" close="end">
+	        when #{item.deptId} then #{item.ancestors}
+	    </foreach>
+	    where dept_id in
+	    <foreach collection="depts" item="item" index="index"
+	        separator="," open="(" close=")">
+	        #{item.deptId}
+	    </foreach>
+	</update>
+	 
+	<update id="updateDeptStatusNormal" parameterType="Long">
+ 	    update sys_dept set status = '0' where dept_id in 
+ 	    <foreach collection="array" item="deptId" open="(" separator="," close=")">
+        	#{deptId}
+        </foreach>
+	</update>
+	
+	<delete id="deleteDeptById" parameterType="Long">
+		update sys_dept set del_flag = '2' where dept_id = #{deptId}
+	</delete>
+
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml
new file mode 100644
index 0000000..3b94b7f
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysDictDataMapper">
+	
+	<resultMap type="SysDictData" id="SysDictDataResult">
+		<id     property="dictCode"   column="dict_code"   />
+		<result property="dictSort"   column="dict_sort"   />
+		<result property="dictLabel"  column="dict_label"  />
+		<result property="dictValue"  column="dict_value"  />
+		<result property="dictType"   column="dict_type"   />
+		<result property="cssClass"   column="css_class"   />
+		<result property="listClass"  column="list_class"  />
+		<result property="isDefault"  column="is_default"  />
+		<result property="status"     column="status"      />
+		<result property="createBy"   column="create_by"   />
+		<result property="createTime" column="create_time" />
+		<result property="updateBy"   column="update_by"   />
+		<result property="updateTime" column="update_time" />
+	</resultMap>
+	
+	<sql id="selectDictDataVo">
+        select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark 
+		from sys_dict_data
+    </sql>
+
+	<select id="selectDictDataList" parameterType="SysDictData" resultMap="SysDictDataResult">
+	    <include refid="selectDictDataVo"/>
+		<where>
+		    <if test="dictType != null and dictType != ''">
+				AND dict_type = #{dictType}
+			</if>
+			<if test="dictLabel != null and dictLabel != ''">
+				AND dict_label like concat('%', #{dictLabel}, '%')
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+		</where>
+		order by dict_sort asc
+	</select>
+	
+	<select id="selectDictDataByType" parameterType="String" resultMap="SysDictDataResult">
+		<include refid="selectDictDataVo"/>
+		where status = '0' and dict_type = #{dictType} order by dict_sort asc
+	</select>
+	
+	<select id="selectDictLabel" resultType="String">
+		select dict_label from sys_dict_data
+		where dict_type = #{dictType} and dict_value = #{dictValue}
+	</select>
+	
+	<select id="selectDictDataById" parameterType="Long" resultMap="SysDictDataResult">
+		<include refid="selectDictDataVo"/>
+		where dict_code = #{dictCode}
+	</select>
+	
+	<select id="countDictDataByType" resultType="Integer">
+	    select count(1) from sys_dict_data where dict_type=#{dictType}  
+	</select>
+	
+	<delete id="deleteDictDataById" parameterType="Long">
+ 		delete from sys_dict_data where dict_code = #{dictCode}
+ 	</delete>
+ 	
+ 	<delete id="deleteDictDataByIds" parameterType="Long">
+ 		delete from sys_dict_data where dict_code in
+ 		<foreach collection="array" item="dictCode" open="(" separator="," close=")">
+ 			#{dictCode}
+        </foreach> 
+ 	</delete>
+	
+	<update id="updateDictData" parameterType="SysDictData">
+ 		update sys_dict_data
+ 		<set>
+ 			<if test="dictSort != null">dict_sort = #{dictSort},</if>
+ 			<if test="dictLabel != null and dictLabel != ''">dict_label = #{dictLabel},</if>
+ 			<if test="dictValue != null and dictValue != ''">dict_value = #{dictValue},</if>
+ 			<if test="dictType != null and dictType != ''">dict_type = #{dictType},</if>
+ 			<if test="cssClass != null">css_class = #{cssClass},</if>
+ 			<if test="listClass != null">list_class = #{listClass},</if>
+ 			<if test="isDefault != null and isDefault != ''">is_default = #{isDefault},</if>
+ 			<if test="status != null">status = #{status},</if>
+ 			<if test="remark != null">remark = #{remark},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where dict_code = #{dictCode}
+	</update>
+	
+	<update id="updateDictDataType" parameterType="String">
+ 		update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType}
+	</update>
+ 	
+ 	<insert id="insertDictData" parameterType="SysDictData">
+ 		insert into sys_dict_data(
+ 			<if test="dictSort != null">dict_sort,</if>
+ 			<if test="dictLabel != null and dictLabel != ''">dict_label,</if>
+ 			<if test="dictValue != null and dictValue != ''">dict_value,</if>
+ 			<if test="dictType != null and dictType != ''">dict_type,</if>
+ 			<if test="cssClass != null and cssClass != ''">css_class,</if>
+ 			<if test="listClass != null and listClass != ''">list_class,</if>
+ 			<if test="isDefault != null and isDefault != ''">is_default,</if>
+ 			<if test="status != null">status,</if>
+ 			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			create_time
+ 		)values(
+ 		    <if test="dictSort != null">#{dictSort},</if>
+ 		    <if test="dictLabel != null and dictLabel != ''">#{dictLabel},</if>
+ 			<if test="dictValue != null and dictValue != ''">#{dictValue},</if>
+ 			<if test="dictType != null and dictType != ''">#{dictType},</if>
+ 			<if test="cssClass != null and cssClass != ''">#{cssClass},</if>
+ 			<if test="listClass != null and listClass != ''">#{listClass},</if>
+ 			<if test="isDefault != null and isDefault != ''">#{isDefault},</if>
+ 			<if test="status != null">#{status},</if>
+ 			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			sysdate()
+ 		)
+	</insert>
+	
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml
new file mode 100644
index 0000000..438d484
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysDictTypeMapper">
+
+	<resultMap type="SysDictType" id="SysDictTypeResult">
+		<id     property="dictId"     column="dict_id"     />
+		<result property="dictName"   column="dict_name"   />
+		<result property="dictType"   column="dict_type"   />
+		<result property="status"     column="status"      />
+		<result property="createBy"   column="create_by"   />
+		<result property="createTime" column="create_time" />
+		<result property="updateBy"   column="update_by"   />
+		<result property="updateTime" column="update_time" />
+	</resultMap>
+	
+	<sql id="selectDictTypeVo">
+        select dict_id, dict_name, dict_type, status, create_by, create_time, remark 
+		from sys_dict_type
+    </sql>
+
+	<select id="selectDictTypeList" parameterType="SysDictType" resultMap="SysDictTypeResult">
+	    <include refid="selectDictTypeVo"/>
+		<where>
+		    <if test="dictName != null and dictName != ''">
+				AND dict_name like concat('%', #{dictName}, '%')
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+			<if test="dictType != null and dictType != ''">
+				AND dict_type like concat('%', #{dictType}, '%')
+			</if>
+			<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+				and date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
+			</if>
+			<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+				and date_format(create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
+			</if>
+	    </where>
+	</select>
+	
+	<select id="selectDictTypeAll" resultMap="SysDictTypeResult">
+		<include refid="selectDictTypeVo"/>
+	</select>
+	
+	<select id="selectDictTypeById" parameterType="Long" resultMap="SysDictTypeResult">
+		<include refid="selectDictTypeVo"/>
+		where dict_id = #{dictId}
+	</select>
+	
+	<select id="selectDictTypeByType" parameterType="String" resultMap="SysDictTypeResult">
+		<include refid="selectDictTypeVo"/>
+		where dict_type = #{dictType}
+	</select>
+	
+	<select id="checkDictTypeUnique" parameterType="String" resultMap="SysDictTypeResult">
+		<include refid="selectDictTypeVo"/>
+		where dict_type = #{dictType} limit 1
+	</select>
+	
+	<delete id="deleteDictTypeById" parameterType="Long">
+ 		delete from sys_dict_type where dict_id = #{dictId}
+ 	</delete>
+ 	
+ 	<delete id="deleteDictTypeByIds" parameterType="Long">
+ 		delete from sys_dict_type where dict_id in
+ 		<foreach collection="array" item="dictId" open="(" separator="," close=")">
+ 			#{dictId}
+        </foreach> 
+ 	</delete>
+
+ 	<update id="updateDictType" parameterType="SysDictType">
+ 		update sys_dict_type
+ 		<set>
+ 			<if test="dictName != null and dictName != ''">dict_name = #{dictName},</if>
+ 			<if test="dictType != null and dictType != ''">dict_type = #{dictType},</if>
+ 			<if test="status != null">status = #{status},</if>
+ 			<if test="remark != null">remark = #{remark},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where dict_id = #{dictId}
+	</update>
+ 	
+ 	<insert id="insertDictType" parameterType="SysDictType">
+ 		insert into sys_dict_type(
+ 			<if test="dictName != null and dictName != ''">dict_name,</if>
+ 			<if test="dictType != null and dictType != ''">dict_type,</if>
+ 			<if test="status != null">status,</if>
+ 			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			create_time
+ 		)values(
+ 			<if test="dictName != null and dictName != ''">#{dictName},</if>
+ 			<if test="dictType != null and dictType != ''">#{dictType},</if>
+ 			<if test="status != null">#{status},</if>
+ 			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			sysdate()
+ 		)
+	</insert>
+	
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml
new file mode 100644
index 0000000..822d665
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysLogininforMapper">
+
+	<resultMap type="SysLogininfor" id="SysLogininforResult">
+		<id     property="infoId"        column="info_id"           />
+		<result property="userName"      column="user_name"         />
+		<result property="status"        column="status"            />
+		<result property="ipaddr"        column="ipaddr"            />
+		<result property="loginLocation" column="login_location"    />
+		<result property="browser"       column="browser"           />
+		<result property="os"            column="os"                />
+		<result property="msg"           column="msg"               />
+		<result property="loginTime"     column="login_time"        />
+	</resultMap>
+
+	<insert id="insertLogininfor" parameterType="SysLogininfor">
+		insert into sys_logininfor (user_name, status, ipaddr, login_location, browser, os, msg, login_time)
+		values (#{userName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate())
+	</insert>
+	
+	<select id="selectLogininforList" parameterType="SysLogininfor" resultMap="SysLogininforResult">
+		select info_id, user_name, ipaddr, login_location, browser, os, status, msg, login_time from sys_logininfor
+		<where>
+			<if test="ipaddr != null and ipaddr != ''">
+				AND ipaddr like concat('%', #{ipaddr}, '%')
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+			<if test="userName != null and userName != ''">
+				AND user_name like concat('%', #{userName}, '%')
+			</if>
+			<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+				AND login_time &gt;= #{params.beginTime}
+			</if>
+			<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+				AND login_time &lt;= #{params.endTime}
+			</if>
+		</where>
+		order by info_id desc
+	</select>
+	
+	<delete id="deleteLogininforByIds" parameterType="Long">
+ 		delete from sys_logininfor where info_id in
+ 		<foreach collection="array" item="infoId" open="(" separator="," close=")">
+ 			#{infoId}
+        </foreach> 
+ 	</delete>
+    
+    <update id="cleanLogininfor">
+        truncate table sys_logininfor
+    </update>
+    
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
new file mode 100644
index 0000000..84e87c9
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+		PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+		"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysMenuMapper">
+
+	<resultMap type="SysMenu" id="SysMenuResult">
+		<id     property="menuId"         column="menu_id"        />
+		<result property="menuName"       column="menu_name"      />
+		<result property="parentName"     column="parent_name"    />
+		<result property="parentId"       column="parent_id"      />
+		<result property="orderNum"       column="order_num"      />
+		<result property="path"           column="path"           />
+		<result property="component"      column="component"      />
+		<result property="query"          column="query"          />
+		<result property="routeName"      column="route_name"     />
+		<result property="isFrame"        column="is_frame"       />
+		<result property="isCache"        column="is_cache"       />
+		<result property="menuType"       column="menu_type"      />
+		<result property="visible"        column="visible"        />
+		<result property="status"         column="status"         />
+		<result property="perms"          column="perms"          />
+		<result property="icon"           column="icon"           />
+		<result property="createBy"       column="create_by"      />
+		<result property="createTime"     column="create_time"    />
+		<result property="updateTime"     column="update_time"    />
+		<result property="updateBy"       column="update_by"      />
+		<result property="remark"         column="remark"         />
+	</resultMap>
+
+	<sql id="selectMenuVo">
+        select menu_id, menu_name, parent_id, order_num, path, component, `query`, route_name, is_frame, is_cache, menu_type, visible, status, ifnull(perms,'') as perms, icon, create_time 
+		from sys_menu
+    </sql>
+    
+    <select id="selectMenuList" parameterType="SysMenu" resultMap="SysMenuResult">
+		<include refid="selectMenuVo"/>
+		<where>
+			<if test="menuName != null and menuName != ''">
+				AND menu_name like concat('%', #{menuName}, '%')
+			</if>
+			<if test="visible != null and visible != ''">
+				AND visible = #{visible}
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+		</where>
+		order by parent_id, order_num
+	</select>
+	
+	<select id="selectMenuTreeAll" resultMap="SysMenuResult">
+		select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.route_name, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
+		from sys_menu m where m.menu_type in ('M', 'C') and m.status = 0
+		order by m.parent_id, m.order_num
+	</select>
+	
+	<select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult">
+		select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.route_name, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
+		from sys_menu m
+		left join sys_role_menu rm on m.menu_id = rm.menu_id
+		left join sys_user_role ur on rm.role_id = ur.role_id
+		left join sys_role ro on ur.role_id = ro.role_id
+		where ur.user_id = #{params.userId}
+		<if test="menuName != null and menuName != ''">
+            AND m.menu_name like concat('%', #{menuName}, '%')
+		</if>
+		<if test="visible != null and visible != ''">
+            AND m.visible = #{visible}
+		</if>
+		<if test="status != null and status != ''">
+            AND m.status = #{status}
+		</if>
+		order by m.parent_id, m.order_num
+	</select>
+    
+    <select id="selectMenuTreeByUserId" parameterType="Long" resultMap="SysMenuResult">
+		select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.route_name, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
+		from sys_menu m
+			 left join sys_role_menu rm on m.menu_id = rm.menu_id
+			 left join sys_user_role ur on rm.role_id = ur.role_id
+			 left join sys_role ro on ur.role_id = ro.role_id
+			 left join sys_user u on ur.user_id = u.user_id
+		where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0  AND ro.status = 0
+		order by m.parent_id, m.order_num
+	</select>
+	
+	<select id="selectMenuListByRoleId" resultType="Long">
+		select m.menu_id
+		from sys_menu m
+            left join sys_role_menu rm on m.menu_id = rm.menu_id
+        where rm.role_id = #{roleId}
+            <if test="menuCheckStrictly">
+              and m.menu_id not in (select m.parent_id from sys_menu m inner join sys_role_menu rm on m.menu_id = rm.menu_id and rm.role_id = #{roleId})
+            </if>
+		order by m.parent_id, m.order_num
+	</select>
+	
+	<select id="selectMenuPerms" resultType="String">
+		select distinct m.perms
+		from sys_menu m
+			 left join sys_role_menu rm on m.menu_id = rm.menu_id
+			 left join sys_user_role ur on rm.role_id = ur.role_id
+	</select>
+
+	<select id="selectMenuPermsByUserId" parameterType="Long" resultType="String">
+		select distinct m.perms
+		from sys_menu m
+			 left join sys_role_menu rm on m.menu_id = rm.menu_id
+			 left join sys_user_role ur on rm.role_id = ur.role_id
+			 left join sys_role r on r.role_id = ur.role_id
+		where m.status = '0' and r.status = '0' and ur.user_id = #{userId}
+	</select>
+	
+	<select id="selectMenuPermsByRoleId" parameterType="Long" resultType="String">
+		select distinct m.perms
+		from sys_menu m
+			 left join sys_role_menu rm on m.menu_id = rm.menu_id
+		where m.status = '0' and rm.role_id = #{roleId}
+	</select>
+	
+	<select id="selectMenuById" parameterType="Long" resultMap="SysMenuResult">
+		<include refid="selectMenuVo"/>
+		where menu_id = #{menuId}
+	</select>
+	
+	<select id="hasChildByMenuId" resultType="Integer">
+	    select count(1) from sys_menu where parent_id = #{menuId}  
+	</select>
+	
+	<select id="checkMenuNameUnique" parameterType="SysMenu" resultMap="SysMenuResult">
+		<include refid="selectMenuVo"/>
+		where menu_name=#{menuName} and parent_id = #{parentId} limit 1
+	</select>
+	
+	<update id="updateMenu" parameterType="SysMenu">
+		update sys_menu
+		<set>
+			<if test="menuName != null and menuName != ''">menu_name = #{menuName},</if>
+			<if test="parentId != null">parent_id = #{parentId},</if>
+			<if test="orderNum != null">order_num = #{orderNum},</if>
+			<if test="path != null and path != ''">path = #{path},</if>
+			<if test="component != null">component = #{component},</if>
+			<if test="query != null">`query` = #{query},</if>
+			<if test="routeName != null">route_name = #{routeName},</if>
+			<if test="isFrame != null and isFrame != ''">is_frame = #{isFrame},</if>
+			<if test="isCache != null and isCache != ''">is_cache = #{isCache},</if>
+			<if test="menuType != null and menuType != ''">menu_type = #{menuType},</if>
+			<if test="visible != null">visible = #{visible},</if>
+			<if test="status != null">status = #{status},</if>
+			<if test="perms !=null">perms = #{perms},</if>
+			<if test="icon !=null and icon != ''">icon = #{icon},</if>
+			<if test="remark != null and remark != ''">remark = #{remark},</if>
+			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+			update_time = sysdate()
+		</set>
+		where menu_id = #{menuId}
+	</update>
+
+	<insert id="insertMenu" parameterType="SysMenu">
+		insert into sys_menu(
+		<if test="menuId != null and menuId != 0">menu_id,</if>
+		<if test="parentId != null and parentId != 0">parent_id,</if>
+		<if test="menuName != null and menuName != ''">menu_name,</if>
+		<if test="orderNum != null">order_num,</if>
+		<if test="path != null and path != ''">path,</if>
+		<if test="component != null and component != ''">component,</if>
+		<if test="query != null and query != ''">`query`,</if>
+		<if test="routeName != null">route_name,</if>
+		<if test="isFrame != null and isFrame != ''">is_frame,</if>
+		<if test="isCache != null and isCache != ''">is_cache,</if>
+		<if test="menuType != null and menuType != ''">menu_type,</if>
+		<if test="visible != null">visible,</if>
+		<if test="status != null">status,</if>
+		<if test="perms !=null and perms != ''">perms,</if>
+		<if test="icon != null and icon != ''">icon,</if>
+		<if test="remark != null and remark != ''">remark,</if>
+		<if test="createBy != null and createBy != ''">create_by,</if>
+		create_time
+		)values(
+		<if test="menuId != null and menuId != 0">#{menuId},</if>
+		<if test="parentId != null and parentId != 0">#{parentId},</if>
+		<if test="menuName != null and menuName != ''">#{menuName},</if>
+		<if test="orderNum != null">#{orderNum},</if>
+		<if test="path != null and path != ''">#{path},</if>
+		<if test="component != null and component != ''">#{component},</if>
+		<if test="query != null and query != ''">#{query},</if>
+		<if test="routeName != null">#{routeName},</if>
+		<if test="isFrame != null and isFrame != ''">#{isFrame},</if>
+		<if test="isCache != null and isCache != ''">#{isCache},</if>
+		<if test="menuType != null and menuType != ''">#{menuType},</if>
+		<if test="visible != null">#{visible},</if>
+		<if test="status != null">#{status},</if>
+		<if test="perms !=null and perms != ''">#{perms},</if>
+		<if test="icon != null and icon != ''">#{icon},</if>
+		<if test="remark != null and remark != ''">#{remark},</if>
+		<if test="createBy != null and createBy != ''">#{createBy},</if>
+		sysdate()
+		)
+	</insert>
+	
+	<delete id="deleteMenuById" parameterType="Long">
+	    delete from sys_menu where menu_id = #{menuId}
+	</delete>
+
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml
new file mode 100644
index 0000000..65d3079
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysNoticeMapper">
+    
+    <resultMap type="SysNotice" id="SysNoticeResult">
+        <result property="noticeId"       column="notice_id"       />
+        <result property="noticeTitle"    column="notice_title"    />
+        <result property="noticeType"     column="notice_type"     />
+        <result property="noticeContent"  column="notice_content"  />
+        <result property="status"         column="status"          />
+        <result property="createBy"       column="create_by"       />
+        <result property="createTime"     column="create_time"     />
+        <result property="updateBy"       column="update_by"       />
+        <result property="updateTime"     column="update_time"     />
+        <result property="remark"         column="remark"          />
+    </resultMap>
+    
+    <sql id="selectNoticeVo">
+        select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark 
+		from sys_notice
+    </sql>
+    
+    <select id="selectNoticeById" parameterType="Long" resultMap="SysNoticeResult">
+        <include refid="selectNoticeVo"/>
+        where notice_id = #{noticeId}
+    </select>
+    
+    <select id="selectNoticeList" parameterType="SysNotice" resultMap="SysNoticeResult">
+        <include refid="selectNoticeVo"/>
+        <where>
+			<if test="noticeTitle != null and noticeTitle != ''">
+				AND notice_title like concat('%', #{noticeTitle}, '%')
+			</if>
+			<if test="noticeType != null and noticeType != ''">
+				AND notice_type = #{noticeType}
+			</if>
+			<if test="createBy != null and createBy != ''">
+				AND create_by like concat('%', #{createBy}, '%')
+			</if>
+		</where>
+    </select>
+    
+    <insert id="insertNotice" parameterType="SysNotice">
+        insert into sys_notice (
+			<if test="noticeTitle != null and noticeTitle != '' ">notice_title, </if>
+			<if test="noticeType != null and noticeType != '' ">notice_type, </if>
+			<if test="noticeContent != null and noticeContent != '' ">notice_content, </if>
+			<if test="status != null and status != '' ">status, </if>
+			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			create_time
+ 		)values(
+			<if test="noticeTitle != null and noticeTitle != ''">#{noticeTitle}, </if>
+			<if test="noticeType != null and noticeType != ''">#{noticeType}, </if>
+			<if test="noticeContent != null and noticeContent != ''">#{noticeContent}, </if>
+			<if test="status != null and status != ''">#{status}, </if>
+			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			sysdate()
+		)
+    </insert>
+	 
+    <update id="updateNotice" parameterType="SysNotice">
+        update sys_notice 
+        <set>
+            <if test="noticeTitle != null and noticeTitle != ''">notice_title = #{noticeTitle}, </if>
+            <if test="noticeType != null and noticeType != ''">notice_type = #{noticeType}, </if>
+            <if test="noticeContent != null">notice_content = #{noticeContent}, </if>
+            <if test="status != null and status != ''">status = #{status}, </if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			update_time = sysdate()
+        </set>
+        where notice_id = #{noticeId}
+    </update>
+	
+    <delete id="deleteNoticeById" parameterType="Long">
+        delete from sys_notice where notice_id = #{noticeId}
+    </delete>
+    
+    <delete id="deleteNoticeByIds" parameterType="Long">
+        delete from sys_notice where notice_id in 
+        <foreach item="noticeId" collection="array" open="(" separator="," close=")">
+            #{noticeId}
+        </foreach>
+    </delete>
+    
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
new file mode 100644
index 0000000..201db07
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysOperLogMapper">
+
+	<resultMap type="SysOperLog" id="SysOperLogResult">
+		<id     property="operId"         column="oper_id"        />
+		<result property="title"          column="title"          />
+		<result property="businessType"   column="business_type"  />
+		<result property="method"         column="method"         />
+		<result property="requestMethod"  column="request_method" />
+		<result property="operatorType"   column="operator_type"  />
+		<result property="operName"       column="oper_name"      />
+		<result property="deptName"       column="dept_name"      />
+		<result property="operUrl"        column="oper_url"       />
+		<result property="operIp"         column="oper_ip"        />
+		<result property="operLocation"   column="oper_location"  />
+		<result property="operParam"      column="oper_param"     />
+		<result property="jsonResult"     column="json_result"    />
+		<result property="status"         column="status"         />
+		<result property="errorMsg"       column="error_msg"      />
+		<result property="operTime"       column="oper_time"      />
+		<result property="costTime"       column="cost_time"      />
+	</resultMap>
+
+	<sql id="selectOperLogVo">
+        select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time
+        from sys_oper_log
+    </sql>
+    
+	<insert id="insertOperlog" parameterType="SysOperLog">
+		insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time)
+        values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate())
+	</insert>
+	
+	<select id="selectOperLogList" parameterType="SysOperLog" resultMap="SysOperLogResult">
+		<include refid="selectOperLogVo"/>
+		<where>
+			<if test="operIp != null and operIp != ''">
+				AND oper_ip like concat('%', #{operIp}, '%')
+			</if>
+			<if test="title != null and title != ''">
+				AND title like concat('%', #{title}, '%')
+			</if>
+			<if test="businessType != null">
+				AND business_type = #{businessType}
+			</if>
+			<if test="businessTypes != null and businessTypes.length > 0">
+			    AND business_type in
+			    <foreach collection="businessTypes" item="businessType" open="(" separator="," close=")">
+		 			#{businessType}
+		        </foreach> 
+			</if>
+			<if test="status != null">
+				AND status = #{status}
+			</if>
+			<if test="operName != null and operName != ''">
+				AND oper_name like concat('%', #{operName}, '%')
+			</if>
+			<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+				AND oper_time &gt;= #{params.beginTime}
+			</if>
+			<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+				AND oper_time &lt;= #{params.endTime}
+			</if>
+		</where>
+		order by oper_id desc
+	</select>
+	
+	<delete id="deleteOperLogByIds" parameterType="Long">
+ 		delete from sys_oper_log where oper_id in
+ 		<foreach collection="array" item="operId" open="(" separator="," close=")">
+ 			#{operId}
+        </foreach> 
+ 	</delete>
+ 	
+ 	<select id="selectOperLogById" parameterType="Long" resultMap="SysOperLogResult">
+		<include refid="selectOperLogVo"/>
+		where oper_id = #{operId}
+	</select>
+	
+	<update id="cleanOperLog">
+        truncate table sys_oper_log
+    </update>
+
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml
new file mode 100644
index 0000000..227c459
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysPostMapper">
+
+	<resultMap type="SysPost" id="SysPostResult">
+		<id     property="postId"        column="post_id"       />
+		<result property="postCode"      column="post_code"     />
+		<result property="postName"      column="post_name"     />
+		<result property="postSort"      column="post_sort"     />
+		<result property="status"        column="status"        />
+		<result property="createBy"      column="create_by"     />
+		<result property="createTime"    column="create_time"   />
+		<result property="updateBy"      column="update_by"     />
+		<result property="updateTime"    column="update_time"   />
+		<result property="remark"        column="remark"        />
+	</resultMap>
+	
+	<sql id="selectPostVo">
+        select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark 
+		from sys_post
+    </sql>
+	
+	<select id="selectPostList" parameterType="SysPost" resultMap="SysPostResult">
+	    <include refid="selectPostVo"/>
+		<where>
+			<if test="postCode != null and postCode != ''">
+				AND post_code like concat('%', #{postCode}, '%')
+			</if>
+			<if test="status != null and status != ''">
+				AND status = #{status}
+			</if>
+			<if test="postName != null and postName != ''">
+				AND post_name like concat('%', #{postName}, '%')
+			</if>
+		</where>
+	</select>
+	
+	<select id="selectPostAll" resultMap="SysPostResult">
+		<include refid="selectPostVo"/>
+	</select>
+	
+	<select id="selectPostById" parameterType="Long" resultMap="SysPostResult">
+		<include refid="selectPostVo"/>
+		where post_id = #{postId}
+	</select>
+	
+	<select id="selectPostListByUserId" parameterType="Long" resultType="Long">
+		select p.post_id
+        from sys_post p
+	        left join sys_user_post up on up.post_id = p.post_id
+	        left join sys_user u on u.user_id = up.user_id
+	    where u.user_id = #{userId}
+	</select>
+	
+	<select id="selectPostsByUserName" parameterType="String" resultMap="SysPostResult">
+		select p.post_id, p.post_name, p.post_code
+		from sys_post p
+			 left join sys_user_post up on up.post_id = p.post_id
+			 left join sys_user u on u.user_id = up.user_id
+		where u.user_name = #{userName}
+	</select>
+	
+	<select id="checkPostNameUnique" parameterType="String" resultMap="SysPostResult">
+		<include refid="selectPostVo"/>
+		 where post_name=#{postName} limit 1
+	</select>
+	
+	<select id="checkPostCodeUnique" parameterType="String" resultMap="SysPostResult">
+		<include refid="selectPostVo"/>
+		 where post_code=#{postCode} limit 1
+	</select>
+	
+	<update id="updatePost" parameterType="SysPost">
+ 		update sys_post
+ 		<set>
+ 			<if test="postCode != null and postCode != ''">post_code = #{postCode},</if>
+ 			<if test="postName != null and postName != ''">post_name = #{postName},</if>
+ 			<if test="postSort != null">post_sort = #{postSort},</if>
+ 			<if test="status != null and status != ''">status = #{status},</if>
+ 			<if test="remark != null">remark = #{remark},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where post_id = #{postId}
+	</update>
+ 	
+ 	<insert id="insertPost" parameterType="SysPost" useGeneratedKeys="true" keyProperty="postId">
+ 		insert into sys_post(
+ 			<if test="postId != null and postId != 0">post_id,</if>
+ 			<if test="postCode != null and postCode != ''">post_code,</if>
+ 			<if test="postName != null and postName != ''">post_name,</if>
+ 			<if test="postSort != null">post_sort,</if>
+ 			<if test="status != null and status != ''">status,</if>
+ 			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			create_time
+ 		)values(
+ 			<if test="postId != null and postId != 0">#{postId},</if>
+ 			<if test="postCode != null and postCode != ''">#{postCode},</if>
+ 			<if test="postName != null and postName != ''">#{postName},</if>
+ 			<if test="postSort != null">#{postSort},</if>
+ 			<if test="status != null and status != ''">#{status},</if>
+ 			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			sysdate()
+ 		)
+	</insert>
+	
+	<delete id="deletePostById" parameterType="Long">
+		delete from sys_post where post_id = #{postId}
+	</delete>
+	
+	<delete id="deletePostByIds" parameterType="Long">
+ 		delete from sys_post where post_id in
+ 		<foreach collection="array" item="postId" open="(" separator="," close=")">
+ 			#{postId}
+        </foreach> 
+ 	</delete>
+
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml
new file mode 100644
index 0000000..7c4139b
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysRoleDeptMapper">
+
+	<resultMap type="SysRoleDept" id="SysRoleDeptResult">
+		<result property="roleId"     column="role_id"      />
+		<result property="deptId"     column="dept_id"      />
+	</resultMap>
+
+	<delete id="deleteRoleDeptByRoleId" parameterType="Long">
+		delete from sys_role_dept where role_id=#{roleId}
+	</delete>
+	
+	<select id="selectCountRoleDeptByDeptId" resultType="Integer">
+	    select count(1) from sys_role_dept where dept_id=#{deptId}
+	</select>
+	
+	<delete id="deleteRoleDept" parameterType="Long">
+ 		delete from sys_role_dept where role_id in
+ 		<foreach collection="array" item="roleId" open="(" separator="," close=")">
+ 			#{roleId}
+        </foreach> 
+ 	</delete>
+	
+	<insert id="batchRoleDept">
+		insert into sys_role_dept(role_id, dept_id) values
+		<foreach item="item" index="index" collection="list" separator=",">
+			(#{item.roleId},#{item.deptId})
+		</foreach>
+	</insert>
+	
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml
new file mode 100644
index 0000000..955d4ee
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysRoleMapper">
+
+	<resultMap type="SysRole" id="SysRoleResult">
+		<id     property="roleId"             column="role_id"               />
+		<result property="roleName"           column="role_name"             />
+		<result property="roleKey"            column="role_key"              />
+		<result property="roleSort"           column="role_sort"             />
+		<result property="dataScope"          column="data_scope"            />
+		<result property="menuCheckStrictly"  column="menu_check_strictly"   />
+		<result property="deptCheckStrictly"  column="dept_check_strictly"   />
+		<result property="status"             column="status"                />
+		<result property="delFlag"            column="del_flag"              />
+		<result property="createBy"           column="create_by"             />
+		<result property="createTime"         column="create_time"           />
+		<result property="updateBy"           column="update_by"             />
+		<result property="updateTime"         column="update_time"           />
+		<result property="remark"             column="remark"                />
+	</resultMap>
+	
+	<sql id="selectRoleVo">
+	    select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly,
+            r.status, r.del_flag, r.create_time, r.remark 
+        from sys_role r
+	        left join sys_user_role ur on ur.role_id = r.role_id
+	        left join sys_user u on u.user_id = ur.user_id
+	        left join sys_dept d on u.dept_id = d.dept_id
+    </sql>
+    
+    <select id="selectRoleList" parameterType="SysRole" resultMap="SysRoleResult">
+		<include refid="selectRoleVo"/>
+		where r.del_flag = '0'
+		<if test="roleId != null and roleId != 0">
+			AND r.role_id = #{roleId}
+		</if>
+		<if test="roleName != null and roleName != ''">
+			AND r.role_name like concat('%', #{roleName}, '%')
+		</if>
+		<if test="status != null and status != ''">
+			AND r.status = #{status}
+		</if>
+		<if test="roleKey != null and roleKey != ''">
+			AND r.role_key like concat('%', #{roleKey}, '%')
+		</if>
+		<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+			and date_format(r.create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
+		</if>
+		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+			and date_format(r.create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
+		</if>
+		<!-- 数据范围过滤 -->
+		${params.dataScope}
+		order by r.role_sort
+	</select>
+    
+	<select id="selectRolePermissionByUserId" parameterType="Long" resultMap="SysRoleResult">
+		<include refid="selectRoleVo"/>
+		WHERE r.del_flag = '0' and ur.user_id = #{userId}
+	</select>
+	
+	<select id="selectRoleAll" resultMap="SysRoleResult">
+		<include refid="selectRoleVo"/>
+	</select>
+	
+	<select id="selectRoleListByUserId" parameterType="Long" resultType="Long">
+		select r.role_id
+        from sys_role r
+	        left join sys_user_role ur on ur.role_id = r.role_id
+	        left join sys_user u on u.user_id = ur.user_id
+	    where u.user_id = #{userId}
+	</select>
+	
+	<select id="selectRoleById" parameterType="Long" resultMap="SysRoleResult">
+		<include refid="selectRoleVo"/>
+		where r.role_id = #{roleId}
+	</select>
+	
+	<select id="selectRolesByUserName" parameterType="String" resultMap="SysRoleResult">
+		<include refid="selectRoleVo"/>
+		WHERE r.del_flag = '0' and u.user_name = #{userName}
+	</select>
+	
+	<select id="checkRoleNameUnique" parameterType="String" resultMap="SysRoleResult">
+		<include refid="selectRoleVo"/>
+		 where r.role_name=#{roleName} and r.del_flag = '0' limit 1
+	</select>
+	
+	<select id="checkRoleKeyUnique" parameterType="String" resultMap="SysRoleResult">
+		<include refid="selectRoleVo"/>
+		 where r.role_key=#{roleKey} and r.del_flag = '0' limit 1
+	</select>
+	
+ 	<insert id="insertRole" parameterType="SysRole" useGeneratedKeys="true" keyProperty="roleId">
+ 		insert into sys_role(
+ 			<if test="roleId != null and roleId != 0">role_id,</if>
+ 			<if test="roleName != null and roleName != ''">role_name,</if>
+ 			<if test="roleKey != null and roleKey != ''">role_key,</if>
+ 			<if test="roleSort != null">role_sort,</if>
+ 			<if test="dataScope != null and dataScope != ''">data_scope,</if>
+ 			<if test="menuCheckStrictly != null">menu_check_strictly,</if>
+ 			<if test="deptCheckStrictly != null">dept_check_strictly,</if>
+ 			<if test="status != null and status != ''">status,</if>
+ 			<if test="remark != null and remark != ''">remark,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			create_time
+ 		)values(
+ 			<if test="roleId != null and roleId != 0">#{roleId},</if>
+ 			<if test="roleName != null and roleName != ''">#{roleName},</if>
+ 			<if test="roleKey != null and roleKey != ''">#{roleKey},</if>
+ 			<if test="roleSort != null">#{roleSort},</if>
+ 			<if test="dataScope != null and dataScope != ''">#{dataScope},</if>
+ 			<if test="menuCheckStrictly != null">#{menuCheckStrictly},</if>
+ 			<if test="deptCheckStrictly != null">#{deptCheckStrictly},</if>
+ 			<if test="status != null and status != ''">#{status},</if>
+ 			<if test="remark != null and remark != ''">#{remark},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			sysdate()
+ 		)
+	</insert>
+	
+	<update id="updateRole" parameterType="SysRole">
+ 		update sys_role
+ 		<set>
+ 			<if test="roleName != null and roleName != ''">role_name = #{roleName},</if>
+ 			<if test="roleKey != null and roleKey != ''">role_key = #{roleKey},</if>
+ 			<if test="roleSort != null">role_sort = #{roleSort},</if>
+ 			<if test="dataScope != null and dataScope != ''">data_scope = #{dataScope},</if>
+ 			<if test="menuCheckStrictly != null">menu_check_strictly = #{menuCheckStrictly},</if>
+ 			<if test="deptCheckStrictly != null">dept_check_strictly = #{deptCheckStrictly},</if>
+ 			<if test="status != null and status != ''">status = #{status},</if>
+ 			<if test="remark != null">remark = #{remark},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where role_id = #{roleId}
+	</update>
+	
+	<delete id="deleteRoleById" parameterType="Long">
+ 		update sys_role set del_flag = '2' where role_id = #{roleId}
+ 	</delete>
+ 	
+ 	<delete id="deleteRoleByIds" parameterType="Long">
+ 	    update sys_role set del_flag = '2' where role_id in
+ 		<foreach collection="array" item="roleId" open="(" separator="," close=")">
+ 			#{roleId}
+        </foreach> 
+ 	</delete>
+ 	
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml
new file mode 100644
index 0000000..cb60a85
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysRoleMenuMapper">
+
+	<resultMap type="SysRoleMenu" id="SysRoleMenuResult">
+		<result property="roleId"     column="role_id"      />
+		<result property="menuId"     column="menu_id"      />
+	</resultMap>
+	
+	<select id="checkMenuExistRole" resultType="Integer">
+	    select count(1) from sys_role_menu where menu_id = #{menuId}
+	</select>
+
+	<delete id="deleteRoleMenuByRoleId" parameterType="Long">
+		delete from sys_role_menu where role_id=#{roleId}
+	</delete>
+	
+	<delete id="deleteRoleMenu" parameterType="Long">
+ 		delete from sys_role_menu where role_id in
+ 		<foreach collection="array" item="roleId" open="(" separator="," close=")">
+ 			#{roleId}
+        </foreach> 
+ 	</delete>
+	
+	<insert id="batchRoleMenu">
+		insert into sys_role_menu(role_id, menu_id) values
+		<foreach item="item" index="index" collection="list" separator=",">
+			(#{item.roleId},#{item.menuId})
+		</foreach>
+	</insert>
+	
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
new file mode 100644
index 0000000..0856cb2
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysUserMapper">
+
+    <resultMap type="SysUser" id="SysUserResult">
+        <id     property="userId"       column="user_id"      />
+        <result property="deptId"       column="dept_id"      />
+        <result property="userName"     column="user_name"    />
+        <result property="nickName"     column="nick_name"    />
+        <result property="email"        column="email"        />
+        <result property="phonenumber"  column="phonenumber"  />
+        <result property="sex"          column="sex"          />
+        <result property="avatar"       column="avatar"       />
+        <result property="password"     column="password"     />
+        <result property="status"       column="status"       />
+        <result property="delFlag"      column="del_flag"     />
+        <result property="loginIp"      column="login_ip"     />
+        <result property="loginDate"    column="login_date"   />
+        <result property="createBy"     column="create_by"    />
+        <result property="createTime"   column="create_time"  />
+        <result property="updateBy"     column="update_by"    />
+        <result property="updateTime"   column="update_time"  />
+        <result property="remark"       column="remark"       />
+        <association property="dept"    javaType="SysDept"         resultMap="deptResult" />
+        <collection  property="roles"   javaType="java.util.List"  resultMap="RoleResult" />
+    </resultMap>
+	
+    <resultMap id="deptResult" type="SysDept">
+        <id     property="deptId"    column="dept_id"     />
+        <result property="parentId"  column="parent_id"   />
+        <result property="deptName"  column="dept_name"   />
+        <result property="ancestors" column="ancestors"   />
+        <result property="orderNum"  column="order_num"   />
+        <result property="leader"    column="leader"      />
+        <result property="status"    column="dept_status" />
+    </resultMap>
+	
+    <resultMap id="RoleResult" type="SysRole">
+        <id     property="roleId"       column="role_id"        />
+        <result property="roleName"     column="role_name"      />
+        <result property="roleKey"      column="role_key"       />
+        <result property="roleSort"     column="role_sort"      />
+        <result property="dataScope"    column="data_scope"     />
+        <result property="status"       column="role_status"    />
+    </resultMap>
+	
+	<sql id="selectUserVo">
+        select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, 
+        d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
+        r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
+        from sys_user u
+		    left join sys_dept d on u.dept_id = d.dept_id
+		    left join sys_user_role ur on u.user_id = ur.user_id
+		    left join sys_role r on r.role_id = ur.role_id
+    </sql>
+    
+    <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
+		select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
+		left join sys_dept d on u.dept_id = d.dept_id
+		where u.del_flag = '0'
+		<if test="userId != null and userId != 0">
+			AND u.user_id = #{userId}
+		</if>
+		<if test="userName != null and userName != ''">
+			AND u.user_name like concat('%', #{userName}, '%')
+		</if>
+		<if test="status != null and status != ''">
+			AND u.status = #{status}
+		</if>
+		<if test="phonenumber != null and phonenumber != ''">
+			AND u.phonenumber like concat('%', #{phonenumber}, '%')
+		</if>
+		<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+			AND date_format(u.create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
+		</if>
+		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+			AND date_format(u.create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
+		</if>
+		<if test="deptId != null and deptId != 0">
+			AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
+		</if>
+		<!-- 数据范围过滤 -->
+		${params.dataScope}
+	</select>
+	
+	<select id="selectAllocatedList" parameterType="SysUser" resultMap="SysUserResult">
+	    select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
+	    from sys_user u
+			 left join sys_dept d on u.dept_id = d.dept_id
+			 left join sys_user_role ur on u.user_id = ur.user_id
+			 left join sys_role r on r.role_id = ur.role_id
+	    where u.del_flag = '0' and r.role_id = #{roleId}
+	    <if test="userName != null and userName != ''">
+			AND u.user_name like concat('%', #{userName}, '%')
+		</if>
+		<if test="phonenumber != null and phonenumber != ''">
+			AND u.phonenumber like concat('%', #{phonenumber}, '%')
+		</if>
+		<!-- 数据范围过滤 -->
+		${params.dataScope}
+	</select>
+	
+	<select id="selectUnallocatedList" parameterType="SysUser" resultMap="SysUserResult">
+	    select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
+	    from sys_user u
+			 left join sys_dept d on u.dept_id = d.dept_id
+			 left join sys_user_role ur on u.user_id = ur.user_id
+			 left join sys_role r on r.role_id = ur.role_id
+	    where u.del_flag = '0' and (r.role_id != #{roleId} or r.role_id IS NULL)
+	    and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and ur.role_id = #{roleId})
+	    <if test="userName != null and userName != ''">
+			AND u.user_name like concat('%', #{userName}, '%')
+		</if>
+		<if test="phonenumber != null and phonenumber != ''">
+			AND u.phonenumber like concat('%', #{phonenumber}, '%')
+		</if>
+		<!-- 数据范围过滤 -->
+		${params.dataScope}
+	</select>
+	
+	<select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
+	    <include refid="selectUserVo"/>
+		where u.user_name = #{userName} and u.del_flag = '0'
+	</select>
+	
+	<select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
+		<include refid="selectUserVo"/>
+		where u.user_id = #{userId}
+	</select>
+	
+	<select id="checkUserNameUnique" parameterType="String" resultMap="SysUserResult">
+		select user_id, user_name from sys_user where user_name = #{userName} and del_flag = '0' limit 1
+	</select>
+	
+	<select id="checkPhoneUnique" parameterType="String" resultMap="SysUserResult">
+		select user_id, phonenumber from sys_user where phonenumber = #{phonenumber} and del_flag = '0' limit 1
+	</select>
+	
+	<select id="checkEmailUnique" parameterType="String" resultMap="SysUserResult">
+		select user_id, email from sys_user where email = #{email} and del_flag = '0' limit 1
+	</select>
+	
+	<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
+ 		insert into sys_user(
+ 			<if test="userId != null and userId != 0">user_id,</if>
+ 			<if test="deptId != null and deptId != 0">dept_id,</if>
+ 			<if test="userName != null and userName != ''">user_name,</if>
+ 			<if test="nickName != null and nickName != ''">nick_name,</if>
+ 			<if test="email != null and email != ''">email,</if>
+ 			<if test="avatar != null and avatar != ''">avatar,</if>
+ 			<if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
+ 			<if test="sex != null and sex != ''">sex,</if>
+ 			<if test="password != null and password != ''">password,</if>
+ 			<if test="status != null and status != ''">status,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			<if test="remark != null and remark != ''">remark,</if>
+ 			create_time
+ 		)values(
+ 			<if test="userId != null and userId != ''">#{userId},</if>
+ 			<if test="deptId != null and deptId != ''">#{deptId},</if>
+ 			<if test="userName != null and userName != ''">#{userName},</if>
+ 			<if test="nickName != null and nickName != ''">#{nickName},</if>
+ 			<if test="email != null and email != ''">#{email},</if>
+ 			<if test="avatar != null and avatar != ''">#{avatar},</if>
+ 			<if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
+ 			<if test="sex != null and sex != ''">#{sex},</if>
+ 			<if test="password != null and password != ''">#{password},</if>
+ 			<if test="status != null and status != ''">#{status},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			<if test="remark != null and remark != ''">#{remark},</if>
+ 			sysdate()
+ 		)
+	</insert>
+	
+	<update id="updateUser" parameterType="SysUser">
+ 		update sys_user
+ 		<set>
+ 			<if test="deptId != null and deptId != 0">dept_id = #{deptId},</if>
+ 			<if test="userName != null and userName != ''">user_name = #{userName},</if>
+ 			<if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
+ 			<if test="email != null ">email = #{email},</if>
+ 			<if test="phonenumber != null ">phonenumber = #{phonenumber},</if>
+ 			<if test="sex != null and sex != ''">sex = #{sex},</if>
+ 			<if test="avatar != null and avatar != ''">avatar = #{avatar},</if>
+ 			<if test="password != null and password != ''">password = #{password},</if>
+ 			<if test="status != null and status != ''">status = #{status},</if>
+ 			<if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if>
+ 			<if test="loginDate != null">login_date = #{loginDate},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			<if test="remark != null">remark = #{remark},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where user_id = #{userId}
+	</update>
+	
+	<update id="updateUserStatus" parameterType="SysUser">
+ 		update sys_user set status = #{status} where user_id = #{userId}
+	</update>
+	
+	<update id="updateUserAvatar" parameterType="SysUser">
+ 		update sys_user set avatar = #{avatar} where user_name = #{userName}
+	</update>
+	
+	<update id="resetUserPwd" parameterType="SysUser">
+ 		update sys_user set password = #{password} where user_name = #{userName}
+	</update>
+	
+	<delete id="deleteUserById" parameterType="Long">
+ 		update sys_user set del_flag = '2' where user_id = #{userId}
+ 	</delete>
+ 	
+ 	<delete id="deleteUserByIds" parameterType="Long">
+ 		update sys_user set del_flag = '2' where user_id in
+ 		<foreach collection="array" item="userId" open="(" separator="," close=")">
+ 			#{userId}
+        </foreach> 
+ 	</delete>
+	
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml
new file mode 100644
index 0000000..2b90bc4
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysUserPostMapper">
+
+	<resultMap type="SysUserPost" id="SysUserPostResult">
+		<result property="userId"     column="user_id"      />
+		<result property="postId"     column="post_id"      />
+	</resultMap>
+
+	<delete id="deleteUserPostByUserId" parameterType="Long">
+		delete from sys_user_post where user_id=#{userId}
+	</delete>
+	
+	<select id="countUserPostById" resultType="Integer">
+	    select count(1) from sys_user_post where post_id=#{postId}  
+	</select>
+	
+	<delete id="deleteUserPost" parameterType="Long">
+ 		delete from sys_user_post where user_id in
+ 		<foreach collection="array" item="userId" open="(" separator="," close=")">
+ 			#{userId}
+        </foreach> 
+ 	</delete>
+	
+	<insert id="batchUserPost">
+		insert into sys_user_post(user_id, post_id) values
+		<foreach item="item" index="index" collection="list" separator=",">
+			(#{item.userId},#{item.postId})
+		</foreach>
+	</insert>
+	
+</mapper> 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml
new file mode 100644
index 0000000..dd72689
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysUserRoleMapper">
+
+	<resultMap type="SysUserRole" id="SysUserRoleResult">
+		<result property="userId"     column="user_id"      />
+		<result property="roleId"     column="role_id"      />
+	</resultMap>
+
+	<delete id="deleteUserRoleByUserId" parameterType="Long">
+		delete from sys_user_role where user_id=#{userId}
+	</delete>
+	
+	<select id="countUserRoleByRoleId" resultType="Integer">
+	    select count(1) from sys_user_role where role_id=#{roleId}  
+	</select>
+	
+	<delete id="deleteUserRole" parameterType="Long">
+ 		delete from sys_user_role where user_id in
+ 		<foreach collection="array" item="userId" open="(" separator="," close=")">
+ 			#{userId}
+        </foreach> 
+ 	</delete>
+	
+	<insert id="batchUserRole">
+		insert into sys_user_role(user_id, role_id) values
+		<foreach item="item" index="index" collection="list" separator=",">
+			(#{item.userId},#{item.roleId})
+		</foreach>
+	</insert>
+	
+	<delete id="deleteUserRoleInfo" parameterType="SysUserRole">
+		delete from sys_user_role where user_id=#{userId} and role_id=#{roleId}
+	</delete>
+	
+	<delete id="deleteUserRoleInfos">
+	    delete from sys_user_role where role_id=#{roleId} and user_id in
+ 	    <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+ 	        #{userId}
+            </foreach> 
+	</delete>
+</mapper> 
\ No newline at end of file
diff --git a/ry.bat b/ry.bat
new file mode 100644
index 0000000..ac1e437
--- /dev/null
+++ b/ry.bat
@@ -0,0 +1,67 @@
+@echo off
+
+rem jarƽ¼¶Ä¿Â¼
+set AppName=ruoyi-admin.jar
+
+rem JVM²ÎÊý
+set JVM_OPTS="-Dname=%AppName%  -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps  -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
+
+
+ECHO.
+	ECHO.  [1] Æô¶¯%AppName%
+	ECHO.  [2] ¹Ø±Õ%AppName%
+	ECHO.  [3] ÖØÆô%AppName%
+	ECHO.  [4] Æô¶¯×´Ì¬ %AppName%
+	ECHO.  [5] ÍË ³ö
+ECHO.
+
+ECHO.ÇëÊäÈëÑ¡ÔñÏîÄ¿µÄÐòºÅ:
+set /p ID=
+	IF "%id%"=="1" GOTO start
+	IF "%id%"=="2" GOTO stop
+	IF "%id%"=="3" GOTO restart
+	IF "%id%"=="4" GOTO status
+	IF "%id%"=="5" EXIT
+PAUSE
+:start
+    for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
+		set pid=%%a
+		set image_name=%%b
+	)
+	if  defined pid (
+		echo %%is running
+		PAUSE
+	)
+
+start javaw %JVM_OPTS% -jar %AppName%
+
+echo  starting¡­¡­
+echo  Start %AppName% success...
+goto:eof
+
+rem º¯Êýstopͨ¹ýjpsÃüÁî²éÕÒpid²¢½áÊø½ø³Ì
+:stop
+	for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
+		set pid=%%a
+		set image_name=%%b
+	)
+	if not defined pid (echo process %AppName% does not exists) else (
+		echo prepare to kill %image_name%
+		echo start kill %pid% ...
+		rem ¸ù¾Ý½ø³ÌID£¬kill½ø³Ì
+		taskkill /f /pid %pid%
+	)
+goto:eof
+:restart
+	call :stop
+    call :start
+goto:eof
+:status
+	for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
+		set pid=%%a
+		set image_name=%%b
+	)
+	if not defined pid (echo process %AppName% is dead ) else (
+		echo %image_name% is running
+	)
+goto:eof
diff --git a/ry.sh b/ry.sh
new file mode 100644
index 0000000..d6a9cf3
--- /dev/null
+++ b/ry.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+# ./ry.sh start 启动 stop 停止 restart 重启 status 状态
+AppName=ruoyi-admin.jar
+
+# JVM参数
+JVM_OPTS="-Dname=$AppName  -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps  -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
+APP_HOME=`pwd`
+LOG_PATH=$APP_HOME/logs/$AppName.log
+
+if [ "$1" = "" ];
+then
+    echo -e "\033[0;31m 未输入操作名 \033[0m  \033[0;34m {start|stop|restart|status} \033[0m"
+    exit 1
+fi
+
+if [ "$AppName" = "" ];
+then
+    echo -e "\033[0;31m 未输入应用名 \033[0m"
+    exit 1
+fi
+
+function start()
+{
+    PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
+
+	if [ x"$PID" != x"" ]; then
+	    echo "$AppName is running..."
+	else
+		nohup java $JVM_OPTS -jar $AppName > /dev/null 2>&1 &
+		echo "Start $AppName success..."
+	fi
+}
+
+function stop()
+{
+    echo "Stop $AppName"
+
+	PID=""
+	query(){
+		PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
+	}
+
+	query
+	if [ x"$PID" != x"" ]; then
+		kill -TERM $PID
+		echo "$AppName (pid:$PID) exiting..."
+		while [ x"$PID" != x"" ]
+		do
+			sleep 1
+			query
+		done
+		echo "$AppName exited."
+	else
+		echo "$AppName already stopped."
+	fi
+}
+
+function restart()
+{
+    stop
+    sleep 2
+    start
+}
+
+function status()
+{
+    PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l`
+    if [ $PID != 0 ];then
+        echo "$AppName is running..."
+    else
+        echo "$AppName is not running..."
+    fi
+}
+
+case $1 in
+    start)
+    start;;
+    stop)
+    stop;;
+    restart)
+    restart;;
+    status)
+    status;;
+    *)
+
+esac
diff --git a/sql/logMenu.sql b/sql/logMenu.sql
new file mode 100644
index 0000000..b512600
--- /dev/null
+++ b/sql/logMenu.sql
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务日志', '3', '1', 'log', 'system/log/index', 1, 0, 'C', '0', '0', 'system:log:list', '#', 'admin', sysdate(), '', null, '任务日志菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务日志查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:log:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务日志新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:log:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务日志修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:log:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务日志删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:log:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务日志导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:log:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file
diff --git a/sql/projectMenu.sql b/sql/projectMenu.sql
new file mode 100644
index 0000000..9be5473
--- /dev/null
+++ b/sql/projectMenu.sql
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目', '3', '1', 'project', 'system/project/index', 1, 0, 'C', '0', '0', 'system:project:list', '#', 'admin', sysdate(), '', null, '项目菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:project:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:project:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:project:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:project:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:project:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file
diff --git a/sql/quartz.sql b/sql/quartz.sql
new file mode 100644
index 0000000..cee613b
--- /dev/null
+++ b/sql/quartz.sql
@@ -0,0 +1,174 @@
+DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
+DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
+DROP TABLE IF EXISTS QRTZ_LOCKS;
+DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
+DROP TABLE IF EXISTS QRTZ_CALENDARS;
+
+-- ----------------------------
+-- 1、存储每一个已配置的 jobDetail 的详细信息
+-- ----------------------------
+create table QRTZ_JOB_DETAILS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    job_name             varchar(200)    not null            comment '任务名称',
+    job_group            varchar(200)    not null            comment '任务组名',
+    description          varchar(250)    null                comment '相关介绍',
+    job_class_name       varchar(250)    not null            comment '执行任务类名称',
+    is_durable           varchar(1)      not null            comment '是否持久化',
+    is_nonconcurrent     varchar(1)      not null            comment '是否并发',
+    is_update_data       varchar(1)      not null            comment '是否更新数据',
+    requests_recovery    varchar(1)      not null            comment '是否接受恢复执行',
+    job_data             blob            null                comment '存放持久化job对象',
+    primary key (sched_name, job_name, job_group)
+) engine=innodb comment = '任务详细信息表';
+
+-- ----------------------------
+-- 2、 存储已配置的 Trigger 的信息
+-- ----------------------------
+create table QRTZ_TRIGGERS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    trigger_name         varchar(200)    not null            comment '触发器的名字',
+    trigger_group        varchar(200)    not null            comment '触发器所属组的名字',
+    job_name             varchar(200)    not null            comment 'qrtz_job_details表job_name的外键',
+    job_group            varchar(200)    not null            comment 'qrtz_job_details表job_group的外键',
+    description          varchar(250)    null                comment '相关介绍',
+    next_fire_time       bigint(13)      null                comment '上一次触发时间(毫秒)',
+    prev_fire_time       bigint(13)      null                comment '下一次触发时间(默认为-1表示不触发)',
+    priority             integer         null                comment '优先级',
+    trigger_state        varchar(16)     not null            comment '触发器状态',
+    trigger_type         varchar(8)      not null            comment '触发器的类型',
+    start_time           bigint(13)      not null            comment '开始时间',
+    end_time             bigint(13)      null                comment '结束时间',
+    calendar_name        varchar(200)    null                comment '日程表名称',
+    misfire_instr        smallint(2)     null                comment '补偿执行的策略',
+    job_data             blob            null                comment '存放持久化job对象',
+    primary key (sched_name, trigger_name, trigger_group),
+    foreign key (sched_name, job_name, job_group) references QRTZ_JOB_DETAILS(sched_name, job_name, job_group)
+) engine=innodb comment = '触发器详细信息表';
+
+-- ----------------------------
+-- 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数
+-- ----------------------------
+create table QRTZ_SIMPLE_TRIGGERS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',
+    trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',
+    repeat_count         bigint(7)       not null            comment '重复的次数统计',
+    repeat_interval      bigint(12)      not null            comment '重复的间隔时间',
+    times_triggered      bigint(10)      not null            comment '已经触发的次数',
+    primary key (sched_name, trigger_name, trigger_group),
+    foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group)
+) engine=innodb comment = '简单触发器的信息表';
+
+-- ----------------------------
+-- 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息
+-- ---------------------------- 
+create table QRTZ_CRON_TRIGGERS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',
+    trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',
+    cron_expression      varchar(200)    not null            comment 'cron表达式',
+    time_zone_id         varchar(80)                         comment '时区',
+    primary key (sched_name, trigger_name, trigger_group),
+    foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group)
+) engine=innodb comment = 'Cron类型的触发器表';
+
+-- ----------------------------
+-- 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)
+-- ---------------------------- 
+create table QRTZ_BLOB_TRIGGERS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',
+    trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',
+    blob_data            blob            null                comment '存放持久化Trigger对象',
+    primary key (sched_name, trigger_name, trigger_group),
+    foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group)
+) engine=innodb comment = 'Blob类型的触发器表';
+
+-- ----------------------------
+-- 6、 以 Blob 类型存储存放日历信息, quartz可配置一个日历来指定一个时间范围
+-- ---------------------------- 
+create table QRTZ_CALENDARS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    calendar_name        varchar(200)    not null            comment '日历名称',
+    calendar             blob            not null            comment '存放持久化calendar对象',
+    primary key (sched_name, calendar_name)
+) engine=innodb comment = '日历信息表';
+
+-- ----------------------------
+-- 7、 存储已暂停的 Trigger 组的信息
+-- ---------------------------- 
+create table QRTZ_PAUSED_TRIGGER_GRPS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',
+    primary key (sched_name, trigger_group)
+) engine=innodb comment = '暂停的触发器表';
+
+-- ----------------------------
+-- 8、 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息
+-- ---------------------------- 
+create table QRTZ_FIRED_TRIGGERS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    entry_id             varchar(95)     not null            comment '调度器实例id',
+    trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',
+    trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',
+    instance_name        varchar(200)    not null            comment '调度器实例名',
+    fired_time           bigint(13)      not null            comment '触发的时间',
+    sched_time           bigint(13)      not null            comment '定时器制定的时间',
+    priority             integer         not null            comment '优先级',
+    state                varchar(16)     not null            comment '状态',
+    job_name             varchar(200)    null                comment '任务名称',
+    job_group            varchar(200)    null                comment '任务组名',
+    is_nonconcurrent     varchar(1)      null                comment '是否并发',
+    requests_recovery    varchar(1)      null                comment '是否接受恢复执行',
+    primary key (sched_name, entry_id)
+) engine=innodb comment = '已触发的触发器表';
+
+-- ----------------------------
+-- 9、 存储少量的有关 Scheduler 的状态信息,假如是用于集群中,可以看到其他的 Scheduler 实例
+-- ---------------------------- 
+create table QRTZ_SCHEDULER_STATE (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    instance_name        varchar(200)    not null            comment '实例名称',
+    last_checkin_time    bigint(13)      not null            comment '上次检查时间',
+    checkin_interval     bigint(13)      not null            comment '检查间隔时间',
+    primary key (sched_name, instance_name)
+) engine=innodb comment = '调度器状态表';
+
+-- ----------------------------
+-- 10、 存储程序的悲观锁的信息(假如使用了悲观锁)
+-- ---------------------------- 
+create table QRTZ_LOCKS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    lock_name            varchar(40)     not null            comment '悲观锁名称',
+    primary key (sched_name, lock_name)
+) engine=innodb comment = '存储的悲观锁信息表';
+
+-- ----------------------------
+-- 11、 Quartz集群实现同步机制的行锁表
+-- ---------------------------- 
+create table QRTZ_SIMPROP_TRIGGERS (
+    sched_name           varchar(120)    not null            comment '调度名称',
+    trigger_name         varchar(200)    not null            comment 'qrtz_triggers表trigger_name的外键',
+    trigger_group        varchar(200)    not null            comment 'qrtz_triggers表trigger_group的外键',
+    str_prop_1           varchar(512)    null                comment 'String类型的trigger的第一个参数',
+    str_prop_2           varchar(512)    null                comment 'String类型的trigger的第二个参数',
+    str_prop_3           varchar(512)    null                comment 'String类型的trigger的第三个参数',
+    int_prop_1           int             null                comment 'int类型的trigger的第一个参数',
+    int_prop_2           int             null                comment 'int类型的trigger的第二个参数',
+    long_prop_1          bigint          null                comment 'long类型的trigger的第一个参数',
+    long_prop_2          bigint          null                comment 'long类型的trigger的第二个参数',
+    dec_prop_1           numeric(13,4)   null                comment 'decimal类型的trigger的第一个参数',
+    dec_prop_2           numeric(13,4)   null                comment 'decimal类型的trigger的第二个参数',
+    bool_prop_1          varchar(1)      null                comment 'Boolean类型的trigger的第一个参数',
+    bool_prop_2          varchar(1)      null                comment 'Boolean类型的trigger的第二个参数',
+    primary key (sched_name, trigger_name, trigger_group),
+    foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group)
+) engine=innodb comment = '同步机制的行锁表';
+
+commit;
\ No newline at end of file
diff --git a/sql/ry_react.sql b/sql/ry_react.sql
new file mode 100644
index 0000000..1f39f31
--- /dev/null
+++ b/sql/ry_react.sql
@@ -0,0 +1,705 @@
+-- ----------------------------
+-- 1、部门表
+-- ----------------------------
+drop table if exists sys_dept;
+create table sys_dept (
+  dept_id           bigint(20)      not null auto_increment    comment '部门id',
+  parent_id         bigint(20)      default 0                  comment '父部门id',
+  ancestors         varchar(50)     default ''                 comment '祖级列表',
+  dept_name         varchar(30)     default ''                 comment '部门名称',
+  order_num         int(4)          default 0                  comment '显示顺序',
+  leader            varchar(20)     default null               comment '负责人',
+  phone             varchar(11)     default null               comment '联系电话',
+  email             varchar(50)     default null               comment '邮箱',
+  status            char(1)         default '0'                comment '部门状态(0正常 1停用)',
+  del_flag          char(1)         default '0'                comment '删除标志(0代表存在 2代表删除)',
+  create_by         varchar(64)     default ''                 comment '创建者',
+  create_time 	    datetime                                   comment '创建时间',
+  update_by         varchar(64)     default ''                 comment '更新者',
+  update_time       datetime                                   comment '更新时间',
+  primary key (dept_id)
+) engine=innodb auto_increment=200 comment = '部门表';
+
+-- ----------------------------
+-- 初始化-部门表数据
+-- ----------------------------
+insert into sys_dept values(100,  0,   '0',          '若依科技',   0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(101,  100, '0,100',      '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(102,  100, '0,100',      '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(103,  101, '0,100,101',  '研发部门',   1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(104,  101, '0,100,101',  '市场部门',   2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(105,  101, '0,100,101',  '测试部门',   3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(106,  101, '0,100,101',  '财务部门',   4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(107,  101, '0,100,101',  '运维部门',   5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(108,  102, '0,100,102',  '市场部门',   1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+insert into sys_dept values(109,  102, '0,100,102',  '财务部门',   2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
+
+
+-- ----------------------------
+-- 2、用户信息表
+-- ----------------------------
+drop table if exists sys_user;
+create table sys_user (
+  user_id           bigint(20)      not null auto_increment    comment '用户ID',
+  dept_id           bigint(20)      default null               comment '部门ID',
+  user_name         varchar(30)     not null                   comment '用户账号',
+  nick_name         varchar(30)     not null                   comment '用户昵称',
+  user_type         varchar(2)      default '00'               comment '用户类型(00系统用户)',
+  email             varchar(50)     default ''                 comment '用户邮箱',
+  phonenumber       varchar(11)     default ''                 comment '手机号码',
+  sex               char(1)         default '0'                comment '用户性别(0男 1女 2未知)',
+  avatar            varchar(100)    default ''                 comment '头像地址',
+  password          varchar(100)    default ''                 comment '密码',
+  status            char(1)         default '0'                comment '帐号状态(0正常 1停用)',
+  del_flag          char(1)         default '0'                comment '删除标志(0代表存在 2代表删除)',
+  login_ip          varchar(128)    default ''                 comment '最后登录IP',
+  login_date        datetime                                   comment '最后登录时间',
+  create_by         varchar(64)     default ''                 comment '创建者',
+  create_time       datetime                                   comment '创建时间',
+  update_by         varchar(64)     default ''                 comment '更新者',
+  update_time       datetime                                   comment '更新时间',
+  remark            varchar(500)    default null               comment '备注',
+  primary key (user_id)
+) engine=innodb auto_increment=100 comment = '用户信息表';
+
+-- ----------------------------
+-- 初始化-用户信息表数据
+-- ----------------------------
+insert into sys_user values(1,  103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员');
+insert into sys_user values(2,  105, 'ry',    '若依', '00', 'ry@qq.com',  '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员');
+
+
+-- ----------------------------
+-- 3、岗位信息表
+-- ----------------------------
+drop table if exists sys_post;
+create table sys_post
+(
+  post_id       bigint(20)      not null auto_increment    comment '岗位ID',
+  post_code     varchar(64)     not null                   comment '岗位编码',
+  post_name     varchar(50)     not null                   comment '岗位名称',
+  post_sort     int(4)          not null                   comment '显示顺序',
+  status        char(1)         not null                   comment '状态(0正常 1停用)',
+  create_by     varchar(64)     default ''                 comment '创建者',
+  create_time   datetime                                   comment '创建时间',
+  update_by     varchar(64)     default ''			       comment '更新者',
+  update_time   datetime                                   comment '更新时间',
+  remark        varchar(500)    default null               comment '备注',
+  primary key (post_id)
+) engine=innodb comment = '岗位信息表';
+
+-- ----------------------------
+-- 初始化-岗位信息表数据
+-- ----------------------------
+insert into sys_post values(1, 'ceo',  '董事长',    1, '0', 'admin', sysdate(), '', null, '');
+insert into sys_post values(2, 'se',   '项目经理',  2, '0', 'admin', sysdate(), '', null, '');
+insert into sys_post values(3, 'hr',   '人力资源',  3, '0', 'admin', sysdate(), '', null, '');
+insert into sys_post values(4, 'user', '普通员工',  4, '0', 'admin', sysdate(), '', null, '');
+
+
+-- ----------------------------
+-- 4、角色信息表
+-- ----------------------------
+drop table if exists sys_role;
+create table sys_role (
+  role_id              bigint(20)      not null auto_increment    comment '角色ID',
+  role_name            varchar(30)     not null                   comment '角色名称',
+  role_key             varchar(100)    not null                   comment '角色权限字符串',
+  role_sort            int(4)          not null                   comment '显示顺序',
+  data_scope           char(1)         default '1'                comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
+  menu_check_strictly  tinyint(1)      default 1                  comment '菜单树选择项是否关联显示',
+  dept_check_strictly  tinyint(1)      default 1                  comment '部门树选择项是否关联显示',
+  status               char(1)         not null                   comment '角色状态(0正常 1停用)',
+  del_flag             char(1)         default '0'                comment '删除标志(0代表存在 2代表删除)',
+  create_by            varchar(64)     default ''                 comment '创建者',
+  create_time          datetime                                   comment '创建时间',
+  update_by            varchar(64)     default ''                 comment '更新者',
+  update_time          datetime                                   comment '更新时间',
+  remark               varchar(500)    default null               comment '备注',
+  primary key (role_id)
+) engine=innodb auto_increment=100 comment = '角色信息表';
+
+-- ----------------------------
+-- 初始化-角色信息表数据
+-- ----------------------------
+insert into sys_role values('1', '超级管理员',  'admin',  1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员');
+insert into sys_role values('2', '普通角色',    'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色');
+
+
+-- ----------------------------
+-- 5、菜单权限表
+-- ----------------------------
+drop table if exists sys_menu;
+create table sys_menu (
+  menu_id           bigint(20)      not null auto_increment    comment '菜单ID',
+  menu_name         varchar(50)     not null                   comment '菜单名称',
+  parent_id         bigint(20)      default 0                  comment '父菜单ID',
+  order_num         int(4)          default 0                  comment '显示顺序',
+  path              varchar(200)    default ''                 comment '路由地址',
+  component         varchar(255)    default null               comment '组件路径',
+  query             varchar(255)    default null               comment '路由参数',
+  route_name        varchar(50)     default ''                 comment '路由名称',
+  is_frame          int(1)          default 1                  comment '是否为外链(0是 1否)',
+  is_cache          int(1)          default 0                  comment '是否缓存(0缓存 1不缓存)',
+  menu_type         char(1)         default ''                 comment '菜单类型(M目录 C菜单 F按钮)',
+  visible           char(1)         default 0                  comment '菜单状态(0显示 1隐藏)',
+  status            char(1)         default 0                  comment '菜单状态(0正常 1停用)',
+  perms             varchar(100)    default null               comment '权限标识',
+  icon              varchar(100)    default '#'                comment '菜单图标',
+  create_by         varchar(64)     default ''                 comment '创建者',
+  create_time       datetime                                   comment '创建时间',
+  update_by         varchar(64)     default ''                 comment '更新者',
+  update_time       datetime                                   comment '更新时间',
+  remark            varchar(500)    default ''                 comment '备注',
+  primary key (menu_id)
+) engine=innodb auto_increment=2000 comment = '菜单权限表';
+
+-- ----------------------------
+-- 初始化-菜单信息表数据
+-- ----------------------------
+-- 一级菜单
+insert into sys_menu values('1', '系统管理', '0', '1', 'system',           null, '', '', 1, 0, 'M', '0', '0', '', 'SettingOutlined',   'admin', sysdate(), '', null, '系统管理目录');
+insert into sys_menu values('2', '系统监控', '0', '2', 'monitor',          null, '', '', 1, 0, 'M', '0', '0', '', 'DashboardOutlined',  'admin', sysdate(), '', null, '系统监控目录');
+insert into sys_menu values('3', '系统工具', '0', '3', 'tool',             null, '', '', 1, 0, 'M', '0', '0', '', 'ToolOutlined',     'admin', sysdate(), '', null, '系统工具目录');
+insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'LinkOutlined',    'admin', sysdate(), '', null, '若依官网地址');
+
+insert into sys_menu values('5', '控制台',   '0', '0', 'dashboard',       null,  '', '', 1, 0, 'M', '1', '0', '', 'AppstoreOutlined', 'admin', sysdate(), '', null, '控制台');
+insert into sys_menu values('6', '个人',     '0', '0', 'account',         null,  '', '', 1, 0, 'M', '1', '0', '', 'ProfileOutlined', 'admin', sysdate(), '', null, '个人');
+
+-- 二级菜单
+insert into sys_menu values('100',  '用户管理', '1',   '1', 'user',       'system/user/index',        '', '', 1, 0, 'C', '0', '0', 'system:user:list',        'user',          'admin', sysdate(), '', null, '用户管理菜单');
+insert into sys_menu values('101',  '角色管理', '1',   '2', 'role',       'system/role/index',        '', '', 1, 0, 'C', '0', '0', 'system:role:list',        'peoples',       'admin', sysdate(), '', null, '角色管理菜单');
+insert into sys_menu values('102',  '菜单管理', '1',   '3', 'menu',       'system/menu/index',        '', '', 1, 0, 'C', '0', '0', 'system:menu:list',        'tree-table',    'admin', sysdate(), '', null, '菜单管理菜单');
+insert into sys_menu values('103',  '部门管理', '1',   '4', 'dept',       'system/dept/index',        '', '', 1, 0, 'C', '0', '0', 'system:dept:list',        'tree',          'admin', sysdate(), '', null, '部门管理菜单');
+insert into sys_menu values('104',  '岗位管理', '1',   '5', 'post',       'system/post/index',        '', '', 1, 0, 'C', '0', '0', 'system:post:list',        'post',          'admin', sysdate(), '', null, '岗位管理菜单');
+insert into sys_menu values('105',  '字典管理', '1',   '6', 'dict',       'system/dict/index',        '', '', 1, 0, 'C', '0', '0', 'system:dict:list',        'dict',          'admin', sysdate(), '', null, '字典管理菜单');
+insert into sys_menu values('106',  '参数设置', '1',   '7', 'config',     'system/config/index',      '', '', 1, 0, 'C', '0', '0', 'system:config:list',      'edit',          'admin', sysdate(), '', null, '参数设置菜单');
+insert into sys_menu values('107',  '通知公告', '1',   '8', 'notice',     'system/notice/index',      '', '', 1, 0, 'C', '0', '0', 'system:notice:list',      'message',       'admin', sysdate(), '', null, '通知公告菜单');
+insert into sys_menu values('108',  '日志管理', '1',   '9', 'log',        '',                         '', '', 1, 0, 'M', '0', '0', '',                        'log',           'admin', sysdate(), '', null, '日志管理菜单');
+insert into sys_menu values('109',  '在线用户', '2',   '1', 'online',     'monitor/online/index',     '', '', 1, 0, 'C', '0', '0', 'monitor:online:list',     'online',        'admin', sysdate(), '', null, '在线用户菜单');
+insert into sys_menu values('110',  '定时任务', '2',   '2', 'job',        'monitor/job/index',        '', '', 1, 0, 'C', '0', '0', 'monitor:job:list',        'job',           'admin', sysdate(), '', null, '定时任务菜单');
+insert into sys_menu values('111',  '数据监控', '2',   '3', 'druid',      'monitor/druid/index',      '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list',      'druid',         'admin', sysdate(), '', null, '数据监控菜单');
+insert into sys_menu values('112',  '服务监控', '2',   '4', 'server',     'monitor/server/index',     '', '', 1, 0, 'C', '0', '0', 'monitor:server:list',     'server',        'admin', sysdate(), '', null, '服务监控菜单');
+insert into sys_menu values('113',  '缓存监控', '2',   '5', 'cache',      'monitor/cache/index',      '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list',      'redis',         'admin', sysdate(), '', null, '缓存监控菜单');
+insert into sys_menu values('114',  '缓存列表', '2',   '6', 'cacheList',  'monitor/cache/list',       '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list',      'redis-list',    'admin', sysdate(), '', null, '缓存列表菜单');
+insert into sys_menu values('115',  '表单构建', '3',   '1', 'build',      'tool/build/index',         '', '', 1, 0, 'C', '0', '0', 'tool:build:list',         'build',         'admin', sysdate(), '', null, '表单构建菜单');
+insert into sys_menu values('116',  '代码生成', '3',   '2', 'gen',        'tool/gen/index',           '', '', 1, 0, 'C', '0', '0', 'tool:gen:list',           'code',          'admin', sysdate(), '', null, '代码生成菜单');
+insert into sys_menu values('117',  '系统接口', '3',   '3', 'swagger',    'tool/swagger/index',       '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list',       'swagger',       'admin', sysdate(), '', null, '系统接口菜单');
+-- 三级菜单
+insert into sys_menu values('500',  '操作日志', '108', '1', 'operlog',    'monitor/operlog/index',    '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list',    'form',          'admin', sysdate(), '', null, '操作日志菜单');
+insert into sys_menu values('501',  '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor',    'admin', sysdate(), '', null, '登录日志菜单');
+-- 用户管理按钮
+insert into sys_menu values('1000', '用户查询', '100', '1',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1001', '用户新增', '100', '2',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1002', '用户修改', '100', '3',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit',           '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1003', '用户删除', '100', '4',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1004', '用户导出', '100', '5',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1005', '用户导入', '100', '6',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1006', '重置密码', '100', '7',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd',       '#', 'admin', sysdate(), '', null, '');
+-- 角色管理按钮
+insert into sys_menu values('1007', '角色查询', '101', '1',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1008', '角色新增', '101', '2',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1009', '角色修改', '101', '3',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit',           '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1010', '角色删除', '101', '4',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1011', '角色导出', '101', '5',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export',         '#', 'admin', sysdate(), '', null, '');
+-- 菜单管理按钮
+insert into sys_menu values('1012', '菜单查询', '102', '1',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1013', '菜单新增', '102', '2',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1014', '菜单修改', '102', '3',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit',           '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1015', '菜单删除', '102', '4',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove',         '#', 'admin', sysdate(), '', null, '');
+-- 部门管理按钮
+insert into sys_menu values('1016', '部门查询', '103', '1',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1017', '部门新增', '103', '2',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1018', '部门修改', '103', '3',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit',           '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1019', '部门删除', '103', '4',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove',         '#', 'admin', sysdate(), '', null, '');
+-- 岗位管理按钮
+insert into sys_menu values('1020', '岗位查询', '104', '1',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1021', '岗位新增', '104', '2',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1022', '岗位修改', '104', '3',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit',           '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1023', '岗位删除', '104', '4',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1024', '岗位导出', '104', '5',  '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export',         '#', 'admin', sysdate(), '', null, '');
+-- 字典管理按钮
+insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit',           '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export',         '#', 'admin', sysdate(), '', null, '');
+-- 参数设置按钮
+insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query',        '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove',       '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export',       '#', 'admin', sysdate(), '', null, '');
+-- 通知公告按钮
+insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query',        '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove',       '#', 'admin', sysdate(), '', null, '');
+-- 操作日志按钮
+insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query',      '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove',     '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export',     '#', 'admin', sysdate(), '', null, '');
+-- 登录日志按钮
+insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query',   '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove',  '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export',  '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock',  '#', 'admin', sysdate(), '', null, '');
+-- 在线用户按钮
+insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query',       '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, '');
+-- 定时任务按钮
+insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query',          '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit',           '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove',         '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus',   '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export',         '#', 'admin', sysdate(), '', null, '');
+-- 代码生成按钮
+insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query',             '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit',              '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import',            '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview',           '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code',              '#', 'admin', sysdate(), '', null, '');
+
+
+-- ----------------------------
+-- 6、用户和角色关联表  用户N-1角色
+-- ----------------------------
+drop table if exists sys_user_role;
+create table sys_user_role (
+  user_id   bigint(20) not null comment '用户ID',
+  role_id   bigint(20) not null comment '角色ID',
+  primary key(user_id, role_id)
+) engine=innodb comment = '用户和角色关联表';
+
+-- ----------------------------
+-- 初始化-用户和角色关联表数据
+-- ----------------------------
+insert into sys_user_role values ('1', '1');
+insert into sys_user_role values ('2', '2');
+
+
+-- ----------------------------
+-- 7、角色和菜单关联表  角色1-N菜单
+-- ----------------------------
+drop table if exists sys_role_menu;
+create table sys_role_menu (
+  role_id   bigint(20) not null comment '角色ID',
+  menu_id   bigint(20) not null comment '菜单ID',
+  primary key(role_id, menu_id)
+) engine=innodb comment = '角色和菜单关联表';
+
+-- ----------------------------
+-- 初始化-角色和菜单关联表数据
+-- ----------------------------
+insert into sys_role_menu values ('2', '1');
+insert into sys_role_menu values ('2', '2');
+insert into sys_role_menu values ('2', '3');
+insert into sys_role_menu values ('2', '4');
+insert into sys_role_menu values ('2', '100');
+insert into sys_role_menu values ('2', '101');
+insert into sys_role_menu values ('2', '102');
+insert into sys_role_menu values ('2', '103');
+insert into sys_role_menu values ('2', '104');
+insert into sys_role_menu values ('2', '105');
+insert into sys_role_menu values ('2', '106');
+insert into sys_role_menu values ('2', '107');
+insert into sys_role_menu values ('2', '108');
+insert into sys_role_menu values ('2', '109');
+insert into sys_role_menu values ('2', '110');
+insert into sys_role_menu values ('2', '111');
+insert into sys_role_menu values ('2', '112');
+insert into sys_role_menu values ('2', '113');
+insert into sys_role_menu values ('2', '114');
+insert into sys_role_menu values ('2', '115');
+insert into sys_role_menu values ('2', '116');
+insert into sys_role_menu values ('2', '117');
+insert into sys_role_menu values ('2', '500');
+insert into sys_role_menu values ('2', '501');
+insert into sys_role_menu values ('2', '1000');
+insert into sys_role_menu values ('2', '1001');
+insert into sys_role_menu values ('2', '1002');
+insert into sys_role_menu values ('2', '1003');
+insert into sys_role_menu values ('2', '1004');
+insert into sys_role_menu values ('2', '1005');
+insert into sys_role_menu values ('2', '1006');
+insert into sys_role_menu values ('2', '1007');
+insert into sys_role_menu values ('2', '1008');
+insert into sys_role_menu values ('2', '1009');
+insert into sys_role_menu values ('2', '1010');
+insert into sys_role_menu values ('2', '1011');
+insert into sys_role_menu values ('2', '1012');
+insert into sys_role_menu values ('2', '1013');
+insert into sys_role_menu values ('2', '1014');
+insert into sys_role_menu values ('2', '1015');
+insert into sys_role_menu values ('2', '1016');
+insert into sys_role_menu values ('2', '1017');
+insert into sys_role_menu values ('2', '1018');
+insert into sys_role_menu values ('2', '1019');
+insert into sys_role_menu values ('2', '1020');
+insert into sys_role_menu values ('2', '1021');
+insert into sys_role_menu values ('2', '1022');
+insert into sys_role_menu values ('2', '1023');
+insert into sys_role_menu values ('2', '1024');
+insert into sys_role_menu values ('2', '1025');
+insert into sys_role_menu values ('2', '1026');
+insert into sys_role_menu values ('2', '1027');
+insert into sys_role_menu values ('2', '1028');
+insert into sys_role_menu values ('2', '1029');
+insert into sys_role_menu values ('2', '1030');
+insert into sys_role_menu values ('2', '1031');
+insert into sys_role_menu values ('2', '1032');
+insert into sys_role_menu values ('2', '1033');
+insert into sys_role_menu values ('2', '1034');
+insert into sys_role_menu values ('2', '1035');
+insert into sys_role_menu values ('2', '1036');
+insert into sys_role_menu values ('2', '1037');
+insert into sys_role_menu values ('2', '1038');
+insert into sys_role_menu values ('2', '1039');
+insert into sys_role_menu values ('2', '1040');
+insert into sys_role_menu values ('2', '1041');
+insert into sys_role_menu values ('2', '1042');
+insert into sys_role_menu values ('2', '1043');
+insert into sys_role_menu values ('2', '1044');
+insert into sys_role_menu values ('2', '1045');
+insert into sys_role_menu values ('2', '1046');
+insert into sys_role_menu values ('2', '1047');
+insert into sys_role_menu values ('2', '1048');
+insert into sys_role_menu values ('2', '1049');
+insert into sys_role_menu values ('2', '1050');
+insert into sys_role_menu values ('2', '1051');
+insert into sys_role_menu values ('2', '1052');
+insert into sys_role_menu values ('2', '1053');
+insert into sys_role_menu values ('2', '1054');
+insert into sys_role_menu values ('2', '1055');
+insert into sys_role_menu values ('2', '1056');
+insert into sys_role_menu values ('2', '1057');
+insert into sys_role_menu values ('2', '1058');
+insert into sys_role_menu values ('2', '1059');
+insert into sys_role_menu values ('2', '1060');
+
+-- ----------------------------
+-- 8、角色和部门关联表  角色1-N部门
+-- ----------------------------
+drop table if exists sys_role_dept;
+create table sys_role_dept (
+  role_id   bigint(20) not null comment '角色ID',
+  dept_id   bigint(20) not null comment '部门ID',
+  primary key(role_id, dept_id)
+) engine=innodb comment = '角色和部门关联表';
+
+-- ----------------------------
+-- 初始化-角色和部门关联表数据
+-- ----------------------------
+insert into sys_role_dept values ('2', '100');
+insert into sys_role_dept values ('2', '101');
+insert into sys_role_dept values ('2', '105');
+
+
+-- ----------------------------
+-- 9、用户与岗位关联表  用户1-N岗位
+-- ----------------------------
+drop table if exists sys_user_post;
+create table sys_user_post
+(
+  user_id   bigint(20) not null comment '用户ID',
+  post_id   bigint(20) not null comment '岗位ID',
+  primary key (user_id, post_id)
+) engine=innodb comment = '用户与岗位关联表';
+
+-- ----------------------------
+-- 初始化-用户与岗位关联表数据
+-- ----------------------------
+insert into sys_user_post values ('1', '1');
+insert into sys_user_post values ('2', '2');
+
+
+-- ----------------------------
+-- 10、操作日志记录
+-- ----------------------------
+drop table if exists sys_oper_log;
+create table sys_oper_log (
+  oper_id           bigint(20)      not null auto_increment    comment '日志主键',
+  title             varchar(50)     default ''                 comment '模块标题',
+  business_type     int(2)          default 0                  comment '业务类型(0其它 1新增 2修改 3删除)',
+  method            varchar(200)    default ''                 comment '方法名称',
+  request_method    varchar(10)     default ''                 comment '请求方式',
+  operator_type     int(1)          default 0                  comment '操作类别(0其它 1后台用户 2手机端用户)',
+  oper_name         varchar(50)     default ''                 comment '操作人员',
+  dept_name         varchar(50)     default ''                 comment '部门名称',
+  oper_url          varchar(255)    default ''                 comment '请求URL',
+  oper_ip           varchar(128)    default ''                 comment '主机地址',
+  oper_location     varchar(255)    default ''                 comment '操作地点',
+  oper_param        varchar(2000)   default ''                 comment '请求参数',
+  json_result       varchar(2000)   default ''                 comment '返回参数',
+  status            int(1)          default 0                  comment '操作状态(0正常 1异常)',
+  error_msg         varchar(2000)   default ''                 comment '错误消息',
+  oper_time         datetime                                   comment '操作时间',
+  cost_time         bigint(20)      default 0                  comment '消耗时间',
+  primary key (oper_id),
+  key idx_sys_oper_log_bt (business_type),
+  key idx_sys_oper_log_s  (status),
+  key idx_sys_oper_log_ot (oper_time)
+) engine=innodb auto_increment=100 comment = '操作日志记录';
+
+
+-- ----------------------------
+-- 11、字典类型表
+-- ----------------------------
+drop table if exists sys_dict_type;
+create table sys_dict_type
+(
+  dict_id          bigint(20)      not null auto_increment    comment '字典主键',
+  dict_name        varchar(100)    default ''                 comment '字典名称',
+  dict_type        varchar(100)    default ''                 comment '字典类型',
+  status           char(1)         default '0'                comment '状态(0正常 1停用)',
+  create_by        varchar(64)     default ''                 comment '创建者',
+  create_time      datetime                                   comment '创建时间',
+  update_by        varchar(64)     default ''                 comment '更新者',
+  update_time      datetime                                   comment '更新时间',
+  remark           varchar(500)    default null               comment '备注',
+  primary key (dict_id),
+  unique (dict_type)
+) engine=innodb auto_increment=100 comment = '字典类型表';
+
+insert into sys_dict_type values(1,  '用户性别', 'sys_user_sex',        '0', 'admin', sysdate(), '', null, '用户性别列表');
+insert into sys_dict_type values(2,  '菜单状态', 'sys_show_hide',       '0', 'admin', sysdate(), '', null, '菜单状态列表');
+insert into sys_dict_type values(3,  '系统开关', 'sys_normal_disable',  '0', 'admin', sysdate(), '', null, '系统开关列表');
+insert into sys_dict_type values(4,  '任务状态', 'sys_job_status',      '0', 'admin', sysdate(), '', null, '任务状态列表');
+insert into sys_dict_type values(5,  '任务分组', 'sys_job_group',       '0', 'admin', sysdate(), '', null, '任务分组列表');
+insert into sys_dict_type values(6,  '系统是否', 'sys_yes_no',          '0', 'admin', sysdate(), '', null, '系统是否列表');
+insert into sys_dict_type values(7,  '通知类型', 'sys_notice_type',     '0', 'admin', sysdate(), '', null, '通知类型列表');
+insert into sys_dict_type values(8,  '通知状态', 'sys_notice_status',   '0', 'admin', sysdate(), '', null, '通知状态列表');
+insert into sys_dict_type values(9,  '操作类型', 'sys_oper_type',       '0', 'admin', sysdate(), '', null, '操作类型列表');
+insert into sys_dict_type values(10, '系统状态', 'sys_common_status',   '0', 'admin', sysdate(), '', null, '登录状态列表');
+
+
+-- ----------------------------
+-- 12、字典数据表
+-- ----------------------------
+drop table if exists sys_dict_data;
+create table sys_dict_data
+(
+  dict_code        bigint(20)      not null auto_increment    comment '字典编码',
+  dict_sort        int(4)          default 0                  comment '字典排序',
+  dict_label       varchar(100)    default ''                 comment '字典标签',
+  dict_value       varchar(100)    default ''                 comment '字典键值',
+  dict_type        varchar(100)    default ''                 comment '字典类型',
+  css_class        varchar(100)    default null               comment '样式属性(其他样式扩展)',
+  list_class       varchar(100)    default null               comment '表格回显样式',
+  is_default       char(1)         default 'N'                comment '是否默认(Y是 N否)',
+  status           char(1)         default '0'                comment '状态(0正常 1停用)',
+  create_by        varchar(64)     default ''                 comment '创建者',
+  create_time      datetime                                   comment '创建时间',
+  update_by        varchar(64)     default ''                 comment '更新者',
+  update_time      datetime                                   comment '更新时间',
+  remark           varchar(500)    default null               comment '备注',
+  primary key (dict_code)
+) engine=innodb auto_increment=100 comment = '字典数据表';
+
+insert into sys_dict_data values(1,  1,  '男',       '0',       'sys_user_sex',        '',   '',        'Y', '0', 'admin', sysdate(), '', null, '性别男');
+insert into sys_dict_data values(2,  2,  '女',       '1',       'sys_user_sex',        '',   '',        'N', '0', 'admin', sysdate(), '', null, '性别女');
+insert into sys_dict_data values(3,  3,  '未知',     '2',       'sys_user_sex',        '',   '',        'N', '0', 'admin', sysdate(), '', null, '性别未知');
+insert into sys_dict_data values(4,  1,  '显示',     '0',       'sys_show_hide',       '',   'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单');
+insert into sys_dict_data values(5,  2,  '隐藏',     '1',       'sys_show_hide',       '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '隐藏菜单');
+insert into sys_dict_data values(6,  1,  '正常',     '0',       'sys_normal_disable',  '',   'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
+insert into sys_dict_data values(7,  2,  '停用',     '1',       'sys_normal_disable',  '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '停用状态');
+insert into sys_dict_data values(8,  1,  '正常',     '0',       'sys_job_status',      '',   'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
+insert into sys_dict_data values(9,  2,  '暂停',     '1',       'sys_job_status',      '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '停用状态');
+insert into sys_dict_data values(10, 1,  '默认',     'DEFAULT', 'sys_job_group',       '',   '',        'Y', '0', 'admin', sysdate(), '', null, '默认分组');
+insert into sys_dict_data values(11, 2,  '系统',     'SYSTEM',  'sys_job_group',       '',   '',        'N', '0', 'admin', sysdate(), '', null, '系统分组');
+insert into sys_dict_data values(12, 1,  '是',       'Y',       'sys_yes_no',          '',   'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是');
+insert into sys_dict_data values(13, 2,  '否',       'N',       'sys_yes_no',          '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '系统默认否');
+insert into sys_dict_data values(14, 1,  '通知',     '1',       'sys_notice_type',     '',   'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知');
+insert into sys_dict_data values(15, 2,  '公告',     '2',       'sys_notice_type',     '',   'success', 'N', '0', 'admin', sysdate(), '', null, '公告');
+insert into sys_dict_data values(16, 1,  '正常',     '0',       'sys_notice_status',   '',   'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
+insert into sys_dict_data values(17, 2,  '关闭',     '1',       'sys_notice_status',   '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '关闭状态');
+insert into sys_dict_data values(18, 99, '其他',     '0',       'sys_oper_type',       '',   'info',    'N', '0', 'admin', sysdate(), '', null, '其他操作');
+insert into sys_dict_data values(19, 1,  '新增',     '1',       'sys_oper_type',       '',   'info',    'N', '0', 'admin', sysdate(), '', null, '新增操作');
+insert into sys_dict_data values(20, 2,  '修改',     '2',       'sys_oper_type',       '',   'info',    'N', '0', 'admin', sysdate(), '', null, '修改操作');
+insert into sys_dict_data values(21, 3,  '删除',     '3',       'sys_oper_type',       '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '删除操作');
+insert into sys_dict_data values(22, 4,  '授权',     '4',       'sys_oper_type',       '',   'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作');
+insert into sys_dict_data values(23, 5,  '导出',     '5',       'sys_oper_type',       '',   'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作');
+insert into sys_dict_data values(24, 6,  '导入',     '6',       'sys_oper_type',       '',   'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作');
+insert into sys_dict_data values(25, 7,  '强退',     '7',       'sys_oper_type',       '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '强退操作');
+insert into sys_dict_data values(26, 8,  '生成代码', '8',       'sys_oper_type',       '',   'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作');
+insert into sys_dict_data values(27, 9,  '清空数据', '9',       'sys_oper_type',       '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '清空操作');
+insert into sys_dict_data values(28, 1,  '成功',     '0',       'sys_common_status',   '',   'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态');
+insert into sys_dict_data values(29, 2,  '失败',     '1',       'sys_common_status',   '',   'danger',  'N', '0', 'admin', sysdate(), '', null, '停用状态');
+
+
+-- ----------------------------
+-- 13、参数配置表
+-- ----------------------------
+drop table if exists sys_config;
+create table sys_config (
+  config_id         int(5)          not null auto_increment    comment '参数主键',
+  config_name       varchar(100)    default ''                 comment '参数名称',
+  config_key        varchar(100)    default ''                 comment '参数键名',
+  config_value      varchar(500)    default ''                 comment '参数键值',
+  config_type       char(1)         default 'N'                comment '系统内置(Y是 N否)',
+  create_by         varchar(64)     default ''                 comment '创建者',
+  create_time       datetime                                   comment '创建时间',
+  update_by         varchar(64)     default ''                 comment '更新者',
+  update_time       datetime                                   comment '更新时间',
+  remark            varchar(500)    default null               comment '备注',
+  primary key (config_id)
+) engine=innodb auto_increment=100 comment = '参数配置表';
+
+insert into sys_config values(1, '主框架页-默认皮肤样式名称',     'sys.index.skinName',            'skin-blue',     'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' );
+insert into sys_config values(2, '用户管理-账号初始密码',         'sys.user.initPassword',         '123456',        'Y', 'admin', sysdate(), '', null, '初始化密码 123456' );
+insert into sys_config values(3, '主框架页-侧边栏主题',           'sys.index.sideTheme',           'theme-dark',    'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' );
+insert into sys_config values(4, '账号自助-验证码开关',           'sys.account.captchaEnabled',    'true',          'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)');
+insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser',      'false',         'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)');
+insert into sys_config values(6, '用户登录-黑名单列表',           'sys.login.blackIPList',         '',              'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)');
+
+
+-- ----------------------------
+-- 14、系统访问记录
+-- ----------------------------
+drop table if exists sys_logininfor;
+create table sys_logininfor (
+  info_id        bigint(20)     not null auto_increment   comment '访问ID',
+  user_name      varchar(50)    default ''                comment '用户账号',
+  ipaddr         varchar(128)   default ''                comment '登录IP地址',
+  login_location varchar(255)   default ''                comment '登录地点',
+  browser        varchar(50)    default ''                comment '浏览器类型',
+  os             varchar(50)    default ''                comment '操作系统',
+  status         char(1)        default '0'               comment '登录状态(0成功 1失败)',
+  msg            varchar(255)   default ''                comment '提示消息',
+  login_time     datetime                                 comment '访问时间',
+  primary key (info_id),
+  key idx_sys_logininfor_s  (status),
+  key idx_sys_logininfor_lt (login_time)
+) engine=innodb auto_increment=100 comment = '系统访问记录';
+
+
+-- ----------------------------
+-- 15、定时任务调度表
+-- ----------------------------
+drop table if exists sys_job;
+create table sys_job (
+  job_id              bigint(20)    not null auto_increment    comment '任务ID',
+  job_name            varchar(64)   default ''                 comment '任务名称',
+  job_group           varchar(64)   default 'DEFAULT'          comment '任务组名',
+  invoke_target       varchar(500)  not null                   comment '调用目标字符串',
+  cron_expression     varchar(255)  default ''                 comment 'cron执行表达式',
+  misfire_policy      varchar(20)   default '3'                comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
+  concurrent          char(1)       default '1'                comment '是否并发执行(0允许 1禁止)',
+  status              char(1)       default '0'                comment '状态(0正常 1暂停)',
+  create_by           varchar(64)   default ''                 comment '创建者',
+  create_time         datetime                                 comment '创建时间',
+  update_by           varchar(64)   default ''                 comment '更新者',
+  update_time         datetime                                 comment '更新时间',
+  remark              varchar(500)  default ''                 comment '备注信息',
+  primary key (job_id, job_name, job_group)
+) engine=innodb auto_increment=100 comment = '定时任务调度表';
+
+insert into sys_job values(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams',        '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
+insert into sys_job values(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')',  '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
+insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)',  '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
+
+
+-- ----------------------------
+-- 16、定时任务调度日志表
+-- ----------------------------
+drop table if exists sys_job_log;
+create table sys_job_log (
+  job_log_id          bigint(20)     not null auto_increment    comment '任务日志ID',
+  job_name            varchar(64)    not null                   comment '任务名称',
+  job_group           varchar(64)    not null                   comment '任务组名',
+  invoke_target       varchar(500)   not null                   comment '调用目标字符串',
+  job_message         varchar(500)                              comment '日志信息',
+  status              char(1)        default '0'                comment '执行状态(0正常 1失败)',
+  exception_info      varchar(2000)  default ''                 comment '异常信息',
+  create_time         datetime                                  comment '创建时间',
+  primary key (job_log_id)
+) engine=innodb comment = '定时任务调度日志表';
+
+
+-- ----------------------------
+-- 17、通知公告表
+-- ----------------------------
+drop table if exists sys_notice;
+create table sys_notice (
+  notice_id         int(4)          not null auto_increment    comment '公告ID',
+  notice_title      varchar(50)     not null                   comment '公告标题',
+  notice_type       char(1)         not null                   comment '公告类型(1通知 2公告)',
+  notice_content    longblob        default null               comment '公告内容',
+  status            char(1)         default '0'                comment '公告状态(0正常 1关闭)',
+  create_by         varchar(64)     default ''                 comment '创建者',
+  create_time       datetime                                   comment '创建时间',
+  update_by         varchar(64)     default ''                 comment '更新者',
+  update_time       datetime                                   comment '更新时间',
+  remark            varchar(255)    default null               comment '备注',
+  primary key (notice_id)
+) engine=innodb auto_increment=10 comment = '通知公告表';
+
+-- ----------------------------
+-- 初始化-公告信息表数据
+-- ----------------------------
+insert into sys_notice values('1', '温馨提醒:2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员');
+insert into sys_notice values('2', '维护通知:2018-07-01 若依系统凌晨维护', '1', '维护内容',   '0', 'admin', sysdate(), '', null, '管理员');
+
+
+-- ----------------------------
+-- 18、代码生成业务表
+-- ----------------------------
+drop table if exists gen_table;
+create table gen_table (
+  table_id          bigint(20)      not null auto_increment    comment '编号',
+  table_name        varchar(200)    default ''                 comment '表名称',
+  table_comment     varchar(500)    default ''                 comment '表描述',
+  sub_table_name    varchar(64)     default null               comment '关联子表的表名',
+  sub_table_fk_name varchar(64)     default null               comment '子表关联的外键名',
+  class_name        varchar(100)    default ''                 comment '实体类名称',
+  tpl_category      varchar(200)    default 'crud'             comment '使用的模板(crud单表操作 tree树表操作)',
+  tpl_web_type      varchar(30)     default ''                 comment '前端模板类型(element-ui模版 element-plus模版)',
+  package_name      varchar(100)                               comment '生成包路径',
+  module_name       varchar(30)                                comment '生成模块名',
+  business_name     varchar(30)                                comment '生成业务名',
+  function_name     varchar(50)                                comment '生成功能名',
+  function_author   varchar(50)                                comment '生成功能作者',
+  gen_type          char(1)         default '0'                comment '生成代码方式(0zip压缩包 1自定义路径)',
+  gen_path          varchar(200)    default '/'                comment '生成路径(不填默认项目路径)',
+  options           varchar(1000)                              comment '其它生成选项',
+  create_by         varchar(64)     default ''                 comment '创建者',
+  create_time 	    datetime                                   comment '创建时间',
+  update_by         varchar(64)     default ''                 comment '更新者',
+  update_time       datetime                                   comment '更新时间',
+  remark            varchar(500)    default null               comment '备注',
+  primary key (table_id)
+) engine=innodb auto_increment=1 comment = '代码生成业务表';
+
+
+-- ----------------------------
+-- 19、代码生成业务表字段
+-- ----------------------------
+drop table if exists gen_table_column;
+create table gen_table_column (
+  column_id         bigint(20)      not null auto_increment    comment '编号',
+  table_id          bigint(20)                                 comment '归属表编号',
+  column_name       varchar(200)                               comment '列名称',
+  column_comment    varchar(500)                               comment '列描述',
+  column_type       varchar(100)                               comment '列类型',
+  java_type         varchar(500)                               comment 'JAVA类型',
+  java_field        varchar(200)                               comment 'JAVA字段名',
+  is_pk             char(1)                                    comment '是否主键(1是)',
+  is_increment      char(1)                                    comment '是否自增(1是)',
+  is_required       char(1)                                    comment '是否必填(1是)',
+  is_insert         char(1)                                    comment '是否为插入字段(1是)',
+  is_edit           char(1)                                    comment '是否编辑字段(1是)',
+  is_list           char(1)                                    comment '是否列表字段(1是)',
+  is_query          char(1)                                    comment '是否查询字段(1是)',
+  query_type        varchar(200)    default 'EQ'               comment '查询方式(等于、不等于、大于、小于、范围)',
+  html_type         varchar(200)                               comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)',
+  dict_type         varchar(200)    default ''                 comment '字典类型',
+  sort              int                                        comment '排序',
+  create_by         varchar(64)     default ''                 comment '创建者',
+  create_time 	    datetime                                   comment '创建时间',
+  update_by         varchar(64)     default ''                 comment '更新者',
+  update_time       datetime                                   comment '更新时间',
+  primary key (column_id)
+) engine=innodb auto_increment=1 comment = '代码生成业务表字段';
\ No newline at end of file
diff --git a/sql/taskMenu.sql b/sql/taskMenu.sql
new file mode 100644
index 0000000..8d07060
--- /dev/null
+++ b/sql/taskMenu.sql
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务', '3', '1', 'task', 'system/task/index', 1, 0, 'C', '0', '0', 'system:task:list', '#', 'admin', sysdate(), '', null, '任务菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:task:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:task:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:task:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:task:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('任务导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:task:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file
diff --git a/sql/userMenu.sql b/sql/userMenu.sql
new file mode 100644
index 0000000..209be39
--- /dev/null
+++ b/sql/userMenu.sql
@@ -0,0 +1,22 @@
+-- 菜单 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目与用户关联', '3', '1', 'user', 'system/user/index', 1, 0, 'C', '0', '0', 'system:user:list', '#', 'admin', sysdate(), '', null, '项目与用户关联菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目与用户关联查询', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:user:query',        '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目与用户关联新增', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:user:add',          '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目与用户关联修改', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:user:edit',         '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目与用户关联删除', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:user:remove',       '#', 'admin', sysdate(), '', null, '');
+
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values('项目与用户关联导出', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:user:export',       '#', 'admin', sysdate(), '', null, '');
\ No newline at end of file