commit 03cc177018354b5d910dee9ae01d0364869c78ae Author: Jane <272005125@qq.com> Date: Tue Dec 26 11:02:39 2023 +0800 init diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..117d385 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +## Salpa:数据模型统一管理平台 + +Salpa是一个非常易用数据模型设计、模型监控、模型管理的集成平台,Salpa可以对市面上已知的大部分数据库,如mysql、oracle、sqlserver、mariadb、postgresql等进行数据模型设计与管理。 +![输入图片说明](diagrams/salpa/%E4%BB%8B%E7%BB%8D%E5%9B%BE%E7%A4%BA.png) + +### 我们为什么需要 Salpa + +Salpa尽所能为您解决数据模型管理可能遇到的问题: + +​ ● 数据业务飞速发展,成百上千规模时候,模型如何管理 + +​ ● 如何从全局角度来审视整个数据体系全貌 + +​ ● 数据模型分散在不同引擎中 + +​ ● 每个引擎数据模型长什么样,业务人员无法实时感知 + +​ ● 数据项目没有办法有效落标引起数据质量问题 + +​ ● 数据时效性问题 + +### Salpa 使用场景 + +​ ● 数据模型设计 + +​ ● 可视化模型管理 + +​ ● 实时数据模型监控 + +​ ● 整合不同引擎数据模型 + +​ ● 编写SQL在线查询各类库 + +​ ● 数据模型变化告警 + +### Salpa 的特性 + +​ ● 前后端分离技术 + +​ ● 简单易用,灵活配置,无需开发 + +​ ● 支持加载动态权限菜单 + +​ ● 多方式轻松权限控制 + +​ ● 支持多种数据库扩展 + +​ ● 模块化,易于扩展 + +### Salpa 的架构图示 + +简单来说,前端UI采用react,typescript,以及antd对页面进行渲染展示,通过Ajax的get和post请求进入后端控制层,调用api接口获取相关数据。请求进入后端后根据不同模块进行业务处理,并通过持久层对数据库进行调用及进行相关数据处理,salpa的架构如下图所示。 + + + +### Salpa 的功能说明 + +系统主要模块包含模型管理、模型监控、在线查询以及系统管理: + + 1. 模型管理主要应用于项目初期,数据模型设计阶段。支持对项目人员管理,数据模型设计以及设计完成后导入数据库。 + + 2. 模型监控,包含主题管理、数据库扩展和告警管理三部分,支持对数据模型进行监控,及时发现模型问题及变化。 + + 3. 在线查询,数据在线查询功能,支持各种数据源。 + + 4. 系统管理,包含用户管理、角色管理、菜单管理、部门管理、岗位管理、字典管理六个模块。 + +![输入图片说明](diagrams/salpa/%E5%8A%9F%E8%83%BD%E5%9B%BE%E7%A4%BA.png) + +### salpa 效果图 + + + +### 环境依赖 + +1.JDK >= 11 (推荐11.0.14版本) + +2.Mysql >= 5.7.0 (推荐5.7.22版本) + +3.Redis >= 3.0 (推荐4.0.8版本) + +4.nginx (推荐nginx/1.99版本) + +### 快速入门 + +关于Salpa[需求文档](/docs/01-Salpa需求规格说明书.doc) + +关于Salpa[设计文档](/docs/02-详细设计说明书.doc) + +关于Salpa[使用手册](/docs/salpa使用手册.docx) + +### 欢迎联系 + +微信: + + + + + 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 [Ϣ] Weḅ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 [Ϣ] ʹJarWeb̡ +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/diagrams/salpa/.keep b/diagrams/salpa/.keep new file mode 100644 index 0000000..e69de29 diff --git a/diagrams/salpa/二维码.jpg b/diagrams/salpa/二维码.jpg new file mode 100644 index 0000000..e486641 Binary files /dev/null and b/diagrams/salpa/二维码.jpg differ diff --git a/diagrams/salpa/介绍图示.png b/diagrams/salpa/介绍图示.png new file mode 100644 index 0000000..c1774c4 Binary files /dev/null and b/diagrams/salpa/介绍图示.png differ diff --git a/diagrams/salpa/功能图示.png b/diagrams/salpa/功能图示.png new file mode 100644 index 0000000..7e6cf6c Binary files /dev/null and b/diagrams/salpa/功能图示.png differ diff --git a/diagrams/salpa/效果图/.keep b/diagrams/salpa/效果图/.keep new file mode 100644 index 0000000..e69de29 diff --git a/diagrams/salpa/效果图/效果图1.png b/diagrams/salpa/效果图/效果图1.png new file mode 100644 index 0000000..1ea6f12 Binary files /dev/null and b/diagrams/salpa/效果图/效果图1.png differ diff --git a/diagrams/salpa/效果图/效果图2.png b/diagrams/salpa/效果图/效果图2.png new file mode 100644 index 0000000..090e544 Binary files /dev/null and b/diagrams/salpa/效果图/效果图2.png differ diff --git a/diagrams/salpa/效果图/效果图3.png b/diagrams/salpa/效果图/效果图3.png new file mode 100644 index 0000000..d64ca40 Binary files /dev/null and b/diagrams/salpa/效果图/效果图3.png differ diff --git a/diagrams/salpa/效果图/效果图4.png b/diagrams/salpa/效果图/效果图4.png new file mode 100644 index 0000000..575ee23 Binary files /dev/null and b/diagrams/salpa/效果图/效果图4.png differ diff --git a/diagrams/salpa/效果图/效果图5.png b/diagrams/salpa/效果图/效果图5.png new file mode 100644 index 0000000..6210419 Binary files /dev/null and b/diagrams/salpa/效果图/效果图5.png differ diff --git a/diagrams/salpa/架构图.png b/diagrams/salpa/架构图.png new file mode 100644 index 0000000..6829986 Binary files /dev/null and b/diagrams/salpa/架构图.png differ diff --git a/docs/.keep b/docs/.keep new file mode 100644 index 0000000..e69de29 diff --git a/docs/01-Salpa需求规格说明书.doc b/docs/01-Salpa需求规格说明书.doc new file mode 100644 index 0000000..ff9f4f2 diff --git a/docs/02-详细设计说明书.doc b/docs/02-详细设计说明书.doc new file mode 100644 index 0000000..4cca974 diff --git a/docs/salpa使用手册.docx b/docs/salpa使用手册.docx new file mode 100644 index 0000000..508298b --- /dev/null +++ b/docs/salpa使用手册.docx @@ -0,0 +1,526 @@ +Salpa:数据模型统一管理平台使用说明 + +1.1 模型管理 + +模型管理用来做数据模型的设计,包括图表、数据修改、数据库连接,分配给不同用户操作不同模型的权限。 +点击菜单栏"模型管理"-->"模型设计",进入模型设计页面,具体如图1.1-1。 + +图1.1-1模型设计 + +1.1.1新建 + 在模型设计的头部导航栏中点击【+】按钮,页面会弹出新建项目,输入必填的信息,点击【保存】,就可以把数据保存到数据内容资产列表,具体如图1.1-2。 + +图1.1-2新建 + +1.1.2删除 + 鼠标移动到想要删除的项目卡片上,会出现删除图标,点击图标,会弹出提示信息,点击【确定】按钮,具体如图1.1-3、1.1-1。 + + --> + 图1.1-3删除按钮 图1.1-1删除提示框 + +1.1.3查询 + 在头部导航栏的搜索框中填入搜索内容,点击【搜索】按钮,就会实现数据查询,具体如图1.1-5。 + +图1.1-5查询 + +1.1.4编辑 + 鼠标移动到想要编辑的项目卡片上,会出现编辑图标,点击图标,会弹出编辑框,输入想要修改的内容,点击【保存】按钮,具体如图1.1-6、1.1-7。 + --> + 图1.1-6编辑按钮 图1.1-7编辑提示框 + +1.1.5权限管理 + 在新建和编辑中的人员管理,是给用户分配权限的。切换用户,所看到的的项目也会有所不同,具体如图1.1-8。 + +图1.1-8权限管理 +1.1.6数据表 + 点击数据表【学生管理系统】或者新建成功,会直接跳转到数据表设计界面。页面头部是返回按钮【<】和数据表的名字,具体如图1.1-9。 + +图1.1-9数据表 +1.1.6.1新建/删除 +点击左侧菜单栏中的图标,会显示【新增/删除】操作,点击【新增】,出现新增弹窗,填好数据点击【确定】,具体如图1.1-10。 + +图1.1-10新建 +点击【删除】,出现删除提示框,点击【确定】,具体如图1.1-11。 + +图1.1-11删除 +1.1.6.1.1逻辑模型新建/删除/编辑 +点击图标会出现【新增/删除/编辑】选项,具体如图1.1-12。 + +图1.1-12选项框 +点击【新增】,出现新增弹窗,输入内容后,点击【确定】,具体如图1.1-13。 + + +图1.1-13新增 + +点击【删除】,出现删除提示框,点击【确定】,具体如图1.1-11。 + +图1.1-11删除 + +点击【信息编辑】,出现编辑弹窗,修改内容后,点击【确定】,具体如图1.1-15。 + +图1.1-15编辑 + +1.1.6.1.2 UML表 + 点击建立好的逻辑模型,点击图标拖拽到页面,创建UML表,可以实现矩形、菱形的表结构,可以改变字体颜色、背景颜色、图形的放大缩小,快捷键【Ctrl+C/Ctrl+V/Delete】可以实现图表的复制粘贴和删除等。具体如图1.1-16。 + +图1.1-16 icon图标 + + 点击创建好的表,可以进行连接,具体如图1.1-17。 + +图1.1-17 UML图 + +1.1.6.1.3 数据表 + 左侧菜单对应的数据表创建好后,打开数据表,右侧会展示数据表中的数据,具体如图1.1-18、1.1-19、1.1-20。 + +图1.1-18 数据表 + +图1.1-19 索引 + +图1.1-20 数据库代码 + +1.1.6.1.1 数据表新建 + 数据表页面的【字段明细】中有【新增】按钮,点击右侧的图标,会显示新增条数选项,选中之后,列表会自动增加对应的空表格,具体如图1.1-21。 + +图1.1-20 数据表新建 + +1.1.6.1.5 数据表删除 + 在数据表页面的【字段明细】中,选中要删除的列表,【置顶】、【上移】、【删除】等功能可以使用,点击【删除】选中的列表就会被删掉,具体如图1.1-21。 + +图1.1-21 数据表删除 +1.1.6.1.6 数据表连接UML + 数据表的列表最左侧的图标,点击开启,在UML图中拖拽的当前数据表,会显示出当条数据;如果被隐藏,在UML图中不会有这条数据。具体如图1.1-22。 + +图1.1-22 UML引入学生表 +当数据列表中被隐藏数据,具体如图1.1-23。 + +图1.1-23 数据表 +UML图中被隐藏数据已经消失,具体如图1.1-21。 + +图1.1-21 UML图 +1.1.6.1.7 索引新建 + + 索引页面有【新增】按钮,点击右侧的图标,会显示新增条数选项,选中之后,列表会自动增加对应的空表格,具体如图1.1-25。 + +图1.1-25 新建 +1.1.6.1.8 索引删除 + 在索引页面选中要删除的列表,【置顶】、【上移】、【删除】等功能可以使用,点击【删除】选中的列表就会被删掉,具体如图1.1-26。 + +图1.1-26 删除 + + +1.1.6.1.9 数据库代码 +数据库代码页面是通过点击上方不同的数据库,会生成不同的代码。还可以查看整个模型数据,具体如图1.1-27、1.1-28。 + + +图1.1-27 MySQL代码 + +图1.1-28 模型数据 +1.1.6.1.10 数据字典 +打开左侧菜单中【数据字典】,每一个页面中点击文本框进行编辑,点击【保存】,保存数据,具体如图1.1-29。 + +图1.1-29 数据字典编辑 + + +1.1.6.2 保存 +所有的数据设计完成之后,点击左上方【保存】图标,将整个页面数据保存下来,具体如图6-30。 + + +图1.1-30 保存 + +1.1.6.3 数据库连接 +点击头部图标中的【数据库】,会出现弹窗,设置数据库配置,点击【测试】,成功会提示【连接成功】,具体如图1.1-31。 + + +图1.1-31 数据库连接测试 +1.1.6.1 数据库提交 +连接成功之后,点击【执行】按钮,将建好的数据库数据传给后台,成功之后返回提示弹窗,具体如图1.1-32。 + +图1.1-32 数据库连接测试 +1.2模型监控 +1.2.1主题管理 +主题管理管理多个主题,能够实现主题的增加,删除,修改,每个主题有一个数据库,数据库管理多个模型,能够实现模型的增加,删除,修改,能够以表格的形式及uml的形式展示数据库中所有表的具体情况。 +点击上方菜单栏"模型监控 -- -- 主题管理"进入主题管理页面,如图1.2.1-1。 + + 图1.2.1-1主题管理 +1.2.1.1新建 +点击左上方的"+"按钮,页面弹出新建框,在框内输入主题名称和主题描述,名称必须填写,描述非必填,点击保存按钮即可新建主题成功,如下图1.2.1-2。 + + 图1.2.1-2创建新主题 +1.2.1.2 查询 +点击搜索栏,输入相应的主题名称,点击搜索按钮进行查询,如下图1.2.1-3. + 图1.2.1-3查询主题 +1.2.1.3编辑 +当鼠标悬浮在主题卡片上,会出现该主题卡片的删除按钮和编辑按钮,如下图1.2.1-1,点击编辑按钮,出现编辑框,修改主题名称和主题内容,主题名称不能修改为空,点击保存按钮即可保存成功,点击取消按钮则恢复原数据,如下图1.2.1-5。 + 图1.2.1-1编辑按钮和删除按钮 + + + 图1.2.1-5编辑主题 + +1.2.1.1主题数据库 +点击主题卡片的主题名称,即可进入该主题数据库,如图1.2.1-6,点击数据库中左上侧的面包屑导航即可返回主题管理页面,如图1.2.1-7。 + + 图1.2.1-6点击进入主题数据库 + + + + 图1.2.1-7主题数据库 +1.2.1.5新建 +点击左上的"+新建"按钮,弹出新建框,输入基础配置和高级配置,带"*"表示必填,保存按钮,确定按钮在高级配置Tab页,点击确定即可创建成功,如图1.2.1-8 + + + 图1.2.1-8创建 +1.2.1.6查询 +根据需求点击上方的输入栏,输入栏的搜索种类有模型名称搜索、数据库名称搜索、Schema名称搜索、数据库类型搜索,输入条件后点击最右侧的搜索按钮即可完成条件查询,如下图1.2.1-9。 + + 图1.2.1-9 查询 + +1.2.1.7编辑 +点击表格中的操作列,鼠标悬浮在"v"按钮时,出现该模型的操作下拉框,如图1.2.1-10,点击"编辑模型"按钮,出现该模型的编辑框,修改后点击保存即可修改成功,如图1.2.1-11。 + 图1.2.1-10 操作 + 图1.2.1-11编辑 +1.2.1.8查看日志 +点击表格中操作的"查看日志"按钮,弹出日志框,如图1.2.1-12. + + 图1.2.1-12查看日志 +1.2.1.9删除 +点击表格中操作的"删除模型"按钮,点击确定即可删除该模型,如图1.2.1-13。 + + 图1.2.1-13删除模型 +1.2.1.10主题详情 +点击表格中操作的"详情"按钮,即可进入到主题详情页面,如图1.2.1-11,点击主题详情页面左上面包屑的主题管理页面返回到主题管理页面,点击数据库返回到数据库页面,如图1.2.1-15。 + + 图1.2.1-11 点击进入主题详情 + + 图1.2.1-15主题详情 +1.2.1.11菜单的缩放与条件查询 +点击左上角缩放按钮即可完成左侧菜单的缩放效果,点击搜索栏输入条件后取消焦点后自动查询,如图1.2.1-16。 + + 图1.2.1-16菜单 +1.2.1.12菜单切换 +点击概览菜单选项时页面显示的是全部表的情况,文档Tab以表的形式展示如图1.2.1-17,UMLTab以uml图展示,如图1.2.1-18。 + + 图1.2.1-17概览菜单文档Tab + + 图1.2.1-18概览菜单UMl Tab +1.2.1.13单表菜单 +除概览以外的均为单表菜单,点击单表菜单显示该表的具体情况,文档Tab以表格形式展示如图1.2.1-19,UML Tab以该表的uml图展示如图1.2.1-20。 + + 图1.2.1-19单表菜单文档Tab + + 图1.2.1-20单表菜单UML Tab +1.2.1.11讨论 +当菜单处于概览选项时,点击页面中Tables表中的讨论列的按钮时如图1.2.1-21,会弹出表格中该条数据的讨论弹窗,点击输入框输入文字后点击提交即可提交讨论,鼠标悬浮在讨论上时如果为本人提交的讨论会出现删除按钮, 点击即可删除,非本人提交的不出现删除按钮,无法删除,如图1.2.1-22。 + + 图1.2.1-21概览菜单讨论 + + 图1.2.1-22讨论 +1.2.1.15同步 +点击上方的同步按钮,即可进行同步操作,同步时弹出当前任务进度条,如图1.2.1-23,当任务完成后,该任务进度条显示已完成,如图1.2.1-21。 + + 图1.2.1-23同步任务进度提示框 + + + 图1.2.1-21同步结束提示框 +1.2.1.16取消同步任务 +任务同步时,鼠标悬浮在该任务的"同步中"进度提示时,出现"点击即可取消"按钮,点击该按钮即可取消同步任务,如图1.2.1-25。 + + 图1.2.1-25取消同步任务 +1.2.1.17查看任务同步情况 +点击"同步"按钮左侧按钮,当未同步时该按钮隐藏,点击该按钮出现同步任务进度情况,如图1.2.1-26。 + + 图1.2.1-26同步任务日志 +1.2.1.18导出 +点击上方的"导出"按钮,点击所需导出的格式即可完成导出,支持的格式有Markdown、UML SVG、UML PNG、Word如图1.2.1-27。 + + 图1.2.1-27 导出 +1.2.1.19切换版本 +点击上方切换菜单下拉框,选择需要切换的版本,即可切换成功如图1.2.1-28。 + + 图1.2.1-28 切换版本 +1.2.1.20显示版本差异 +点击上方的"显示版本差异按钮",出现对比版本下拉框,如图1.2.1-29。 + + 图1.2.1-29显示版本差异 +1.2.1.21对比版本 +默认当前版本和对比版本都为最新版本,切换当前版本和对比版本时,概览会显示数据库中所有表的变化情况,绿色代表新增表,红色代表删除表,橙色代表修改表,如图1.2.1-30,图1.2.1-31,单表会显示其数据变化情况,绿色代表该表新增数据,红色代表该表删除数据,橙色代表该表修改数据。如图1.2.1-32。 + + 图1.2.1-30概览菜单对比版本效果图 + + + 图1.2.1-31概览菜单对比版本效果图 + + 图1.2.1-32单表对比版本效果图 +1.2.2数据库拓展 +数据库拓展用来管理数据库的类型,同时也可以完成数据库的新增,修改和删除。 +点击上方菜单栏"模型监控 -- -- 数据库拓展"进入数据库拓展页面,如图1.2.2-1。 + + + 图1.2.2-1数据库拓展 +1.2.2.1新建 +点击页面上方的"+"按钮进行新建数据库,弹出新建框如图1.2.2-2,点击"驱动获取方式"下拉框,默认驱动方式为链接下载,在"JDBC驱动下载地址"输入框输入地址后点击"驱动类名"按钮获取驱动类名,如图1.2.2-3,当驱动方式切换成本地导入后,点击"点击上传文件"按钮上传文件后,点击"驱动类名"按钮获取驱动类名,如图1.2.2-1 全部信息输入后点击"确定"按钮创建新的数据库。 + + + 图1.2.2-2新建数据库 + + 图1.2.2-3链接下载 + + 图1.2.2-1本地导入 +1.2.2.2查询 +点击查询输入框输入需要查询的数据库类型名称后点击搜索按钮,如图1.2.2-5。 + + 图1.2.2-5查询 +1.2.2.3删除 +当鼠标悬浮在数据库卡片上,会出现该数据库的删除按钮和编辑按钮,如图1.2.2-6,点击删除后,出现删除框,点击确定即可删除,如图1.2.2-7。 + + 图1.2.2-6 删除按钮和编辑按钮 + + 图1.2.2-7删除 +1.2.2.1编辑 +鼠标悬浮在数据库卡片上,点击编辑按钮,出现该数据库的编辑框,根据需求修改信息,点击"确定"按钮即可,如图1.2.2-8。 + + 图1.2.2-8编辑 +1.2.3告警管理 +告警管理页面是当数据库发生改变生成新的版本时,将会自动生成一条告警信息,通过点击处理来确认是否已经查看过此条告警信息,同时显示已读者即处理人的名称。 +点击上方菜单栏"模型监控 -- -- 告警管理"进入告警管理页面,如图1.2.3-1。 + + 图1.2.3-1告警管理 +1.2.3.1处理 +点击页面中表格的操作列,点击"待处理"按钮进行处理,处理完成后,"待处理"按钮变为"已处理"按钮,显示为"已处理"的数据不能再次操作,处理人列显示该条数据的处理人的名称,告警状态由未读改为已读 如图1.2.3-2。 + + 图1.2.3-2处理 +1.3 在线查询 + +在线查询用来做数据转换、集成和可视化,支持各种数据源。 +点击菜单栏"在线查询"-->"数据库查询",进入数据库查询页面,具体如图1.3-1。 + + +图1.3-1 数据库查询 + + +1.3.1查询 + 在顶部【选择数据源】中选择数据源,左侧会展示出相对应的数据库,具体如图1.3-2。 + +图1.3-2 查询 +1.3.2 Sql输入 + 右侧输入sql语句,用来查询数据库中的数据,具体如图7-3。 + +图1.3-3 sql语句 +1.3.3 数据库 + 点击顶部【执行】按钮,会在sql语句底部展示出列表数据,具体如图1.3-1。 + +图1.3-4 数据库列表 + + 列表具有排序、滚动条、分页功能,具体如图1.3-5。 + +图1.3-5 数据库列表 +1.4 系统管理 +1.4.1 用户管理 + ⽤户管理可以管理系统的所有账户,控制账户的各种权限。⽤户管理模块只有系统管理员有权限执⾏操作。 + 点击菜单栏"系统管理"-->"⽤户管理",进⼊⽤户管理⻚⾯,具体如图1.4.1-1。 + +图1.4.1-1 用户管理页面 +1.4.1.1 新增用户 + 在⽤户管理,点击左上⻆的 "新建"按钮即可创建⼀个新⽤户,具体如图1.4.1-2。 + +图1.4.1-2 新增用户 +进⼊表单⻚后,需要填写⽤户的基础信息,并为该⽤户设置⼀个密码,平台⽬前不会校验邮箱的真实性,请确保邮箱填写正确,具体如图1.4.1-3。 +图1.4.1-3 新增用户弹框 +点击保存以后就可以在列表⻚看⻅刚刚新增的⽤户了 +1.4.1.2 修改用户 +在⽤户管理,点击⽤户列表的 "编辑"按钮即可修改⽤户,具体如图1.4.1-4和1.4.1-5。 +图1.4.1-4 修改用户 + +图1.4.1-5 修改用户弹框 +1.4.1.3 删除用户 +在⽤户管理,点击⽤户列表的 "删除"按钮即可删除⽤户,删除需⼆次确认,确认后将删除⽤户选中多个⽤户后,⽀持批量删除⽤户,具体如图1.4.1-6。 +图1.4.1-6 删除用户 + +1.4.1.4 密码重置 +在⽤户管理,点击⽤户列表的 "密码"按钮即可修改⽤户密码,修改⽤户密码需要输⼊原密码,具体如图1.4.1-7。 +图1.4.1-7 密码重置 +1.4.1.5 导出用户 +在⽤户列表中选中要导出的⽤户,⽀持多选,点击右上⽅"导出"按钮,导出⽤户信息。如果不选中⽤户信息,直接点击"导出",将导出全部⽤户信息,具体如图1.4.1-8。 + +图1.4.1-8 导出用户 +1.4.1.6 查询用户 +在⽤户列表中上⽅⽀持进⾏查询搜索、重置等功能,查询选项默认三项:部⻔ID、⽤户账号、⽤户邮箱,点击"展开"按钮后,查询选项为五项,分别为:部⻔ID、⽤户账号、⽤户邮箱、⼿机号码、账号状态,具体如图1.4.1-9。 + +图1.4.1-9查询用户 +1.4.1.7 设置 +在⽤户列表中上⽅⽀持刷新列表、配置列表密度和列配置,具体如图1.4.1-10。 + +图1.4.1-10设置 +1.4.2角色管理 +⻆⾊管理对系统所有⻆⾊进⾏管理,为不同⻆⾊设置不同的菜单权限。⻆⾊管理模块只有系统管理员有权限执⾏操作。 +点击菜单栏"系统管理"-->"⻆⾊管理",进⼊⻆⾊管理⻚⾯,具体如图1.4.2-1。 + +图1.4.2-1角色管理 +1.4.2.1 新建角色 +在⻆⾊管理,点击左上⻆的 "新建"按钮即可创建⼀个新⻆⾊,具体如图1.4.2-2。 + +图1.4.2.2新建角色 +进⼊表单⻚后,需要填写⻆⾊的基础信息,点击确定,完成⻆⾊新建,具体如图1.4.2.3 + +图1.4.2.3新建角色弹框 + +1.4.2.2 修改角色 +在⻆⾊管理,点击⻆⾊列表的 "编辑"按钮即可修改⻆⾊,具体如图1.4.2-4、1.4.2-5。 + +图1.4.2-4修改角色 +图1.4.2-5修改弹框 +1.4.2.3 删除角色 +在⻆⾊管理,点击⻆⾊列表的 "删除"按钮即可删除⻆⾊,删除需⼆次确认,确认后将删除⻆⾊;选中多个⻆⾊后,⽀持批量删除。具体如图1.4.2-6。 + +图1.4.2-6删除角色 +1.4.2.4 导出角色 +在⻆⾊列表中选中要导出的⻆⾊,⽀持多选,点击右上⽅"导出"按钮,导出⻆⾊信息。如果不选中⻆⾊信息,直接点击"导出",将导出全部⻆⾊信息,具体如图1.4.2-7。 + +图1.4.2-7导出角色 +1.4.2.5 查询角色 +在⻆⾊列表中上⽅⽀持进⾏查询搜索、重置等功能,查询选项默认包括三项:⻆⾊名称、⻆⾊权限字符串、显示顺序,点击"展开"按钮,查询选项为四项,分别为:⻆⾊名称、⻆⾊权限字符串、显示顺序、⻆⾊状态。具体如图1.4.2-8。 + +图1.4.2-8查询角色 +1.4.2.6 设置 +在⻆⾊列表中上⽅⽀持刷新列表、配置列表密度和列配置,具体如图1.4.2-9。 + +图1.4.2-9设置 +1.4.3菜单管理 +菜单管理⽀持控制平台所有菜单名称、图标、可⻅状态等。菜单管理模块只有系统管理员有权限执⾏操作。 +点击菜单栏"系统管理"-->"菜单管理",进⼊菜单管理⻚⾯,具体如图 +1.4.3-1。 + +图1.4.3-1菜单管理 +1.4.3.1 新建菜单 +在菜单管理,点击左上⻆的 "新建"按钮即可创建⼀个新菜单,菜单存在层级关系,因此新建时,需要选择⽗级菜单。具体如图1.4.3-2。 + + +图1.4.3-2新建菜单 +进⼊表单⻚后,需要填写菜单信息,点击确定,完成菜单新建,具体如图1.4.3-3。 + +图1.4.3-3新建菜单弹框 + +1.4.3.2 修改菜单 +在部⻔管理,点击部⻔列表的 "编辑"按钮即可修改⻆⾊,具体如图1.4.3-4、1.4.3-5。 + +图1.4.3-4修改菜单 +图1.4.3-5修改菜单弹框 + +1.4.3.3 删除菜单 +在菜单管理,点击菜单列表的 "删除"按钮即可删除菜单,删除需⼆次确认,确认后将删除菜单;选中多个菜单后,⽀持批量删除。具体如图1.4.3-6。 + +图1.4.3-6删除菜单 +1.4.3.4 查询菜单 +在菜单列表中上⽅⽀持进⾏查询搜索、重置等功能,查询选项默认包括三项:菜单名称、权限标识、菜单类型,点击"展开",查询选项包括:菜单名称、权限标识、菜单类型、菜单状态,具体如图1.4.3-7。 + +图1.4.3-7查询菜单 + +1.4.3.5 设置 +在菜单列表中上⽅⽀持刷新列表、配置列表密度和列配置,具体如图1.4.3-8。 + +图1.4.3-8设置 +1.4.4部门管理 +部⻔管理模块只有系统管理员有权限执⾏操作。点击菜单栏"系统管理"-->"部⻔管理",进⼊部⻔管理⻚⾯,具体如图1.4.4-1。 + +图1.4.4-1部门管理 +1.4.4.1新建部门 +在菜单列表中上⽅⽀持刷新列表、配置列表密度和列配置,具体如图1.4.4-2。 + +图1.4.4-2新建部门 +进⼊表单⻚后,需要填写部⻔的信息,点击确定,完成部⻔新建,具体如图1.4.4-3。 + +图1.4.4-3新建部门弹框 +1.4.4.2修改部门 +在部⻔管理,点击部⻔列表的 "编辑"按钮即可修改⻆⾊,具体如图1.4.4-4、1.4-4-5。 + +图1.4.4-4修改部门 +图1.4.4-5修改部门弹框 +1.4.4.3删除部门 +在部⻔管理,点击部⻔列表的 "编辑"按钮即可修改⻆⾊,具体如图1.4.4-6。 + +图1.4.4-6删除部门 +1.4.4.4查询部门 +在部⻔列表中上⽅⽀持进⾏查询搜索、重置等功能,查询选项默认包括三项:部⻔名称、显示顺序、负责⼈,点击"展开",查询选项包括:部⻔名称、显示顺序、负责⼈、联系电话、邮箱、部⻔状态,具体如图1.4.4-7。 + +图1.4.4-7查询部门 +1.4.4.5设置 +在⻆⾊列表中上⽅⽀持刷新列表、配置列表密度和列配置,具体如图1.4.4-8。 + +图1.4.4-8设置 +1.4.5岗位管理 +岗位管理将岗位信息展示在列表中,实现岗位的增删改查操作。点击菜单栏"系统管理"-->"岗位管理",进⼊岗位管理⻚⾯,具体如图1.4.5-1。 + +图1.4.5-1岗位管理 +1.4.5.1新建岗位 +点击右侧的【新建】按钮,出现新建弹窗,填写完成,点击【确定】新建成功,具体如图1.4.5-2。 + +图1.4.5-2新建岗位 +1.4.5.2删除岗位 +点击想要删除⾏的【删除】按钮,弹出提示框,点击【确定】,删除成功;选中多个岗位后,⽀持批量删除。具体如图1.4.5-3。 + +图1.4.5-3删除岗位 +1.4.5.3编辑岗位 +点击想要编辑⾏的【编辑】按钮,弹出修改窗⼝,点击【确定】,修改成功,具体如图1.4.5-4。 + +图1.4.5-4编辑岗位 +1.4.5.4查询岗位 +顶部栏中输⼊想要查询的条件,点击右侧的【查询】,实现搜索,默认搜索包括三项:岗位编码、岗位名称、显示顺序,展开后新增状态筛选,具体如图1.4.5-5。 + +图1.4.5-5查询岗位 +1.4.5.5导出岗位 +点击右侧的【导出】按钮,会⽣成Excel⽂件,选择位置,点击【保存】,具体如图1.4.5-6。 + +图1.4.5-6导出岗位 +1.4.6字典管理 +字典管理是为数据库提供的⼀系列数据,可以进⾏增删改查操作。点击菜单栏"系统管理"-->"字典管理",进⼊系统管理⻚⾯,具体如图1.4.6-1。 + +图1.4.6-1字典管理 +1.4.6.1新建字典 +点击右侧的【新建】按钮,出现新建弹窗,填写完成,点击【确定】新建成功,具体如图1.4.6-2。 + + +图1.4.6-2新建字典 + +1.4.6.2删除字典 +点击想要删除⾏的【删除】按钮,弹出提示框, 点击【确定】,删除成功;选中多个字典后,⽀持批量删除。1.4.6-3。 + +图1.4.6-3删除字典 +1.4.6.3编辑字典 +点击想要编辑⾏的【编辑】按钮,弹出修改窗⼝,点击【确定】,修改成功,具体如图1.4.6-4。 + +图1.4.6-4编辑字典 +1.4.6.4查询字典 +顶部栏中输⼊想要查询的条件,点击右侧的【查询】,实现搜索,具体如图1.4.6-5。 + +图1.4.6-5查询字典 +1.4.6.5导出字典 +点击右侧的【导出】按钮,会⽣成Excel⽂件,选择位置,点击【保存】,具体如图1.4.6-6。 + +图1.4.6-6导出字典 + +1.4.6.6字典数据 +点击【字典类型】,会跳转到该列管理的数据中,具体如图1.4.6-7、1.4.6-8。 + +图1.4.6-7字典类型 +图1.4.6-8字典数据 +1.4.6.6.1字典数据新建 +点击右侧的【新建】按钮,出现新建弹窗,填写完成,点击【确定】新建成功,具体如图1.4.6.6-1。 + +图1.4.6.6-1新建 + +1.4.6.6.2字典数据删除 +点击想要删除⾏的【删除】按钮,弹出提示框,点击【确定】,删除成功,具体如图1.4.6.6-2。 + +图1.4.6.6-2删除 +1.4.6.6.3字典数据编辑 +点击想要编辑⾏的【编辑】按钮,弹出修改窗⼝,点击【确定】,修改成功,具体如图1.4.6.6-3。 + +图1.4.6.6-3编辑 +1.4.6.6.4字典数据查询 +顶部栏中输⼊想要查询的条件,点击右侧的【查询】,实现搜索,具体如图1.4.6.6-4。 + +图1.4.6.6-4查询 +1.4.6.6.5字典数据导出 +点击右侧的【导出】按钮,会⽣成Excel⽂件,选择位置,点击【保存】,具体如图1.4.6.6-5。 + +图1.4.6.6-5导出 + diff --git a/docs/salpa安装手册.docx b/docs/salpa安装手册.docx new file mode 100644 index 0000000..a2bc5d4 --- /dev/null +++ b/docs/salpa安装手册.docx @@ -0,0 +1,3 @@ +1)准备工作检查服务器是否安装以下软件或程序JDK >= 11 (推荐11版本)Mysql >= 5.7.0 (推荐5.7版本)Redis >= 3.0Maven >= 3.0Node >= 122)创建数据库1.创建新数据库3)必要配置 (1)修改数据库连接 (2)修改服务器配置(3)修改redis配置false(4)修改文件路径,根据实际情况调整。4)后端部署oo 打包工程文件 +在项目的bin目录下执行package.bat打包Web工程,生成war/jar包文件。生成在metaops/salpa-admin模块下target文件夹oo 部署工程文件 +1、 jar部署方式(1)将打包好的jar文件上传至服务器(2)进入jar包所在目录使用命令启动jar包2、 war部署方式(1)metaops/pom.xml中的packaging修改为war(2)放入tomcat服务器webapps5)前端部署1.安装npm依赖进入前端项目cd metaops-front安装依赖npm install --registry=https://registry.npm.taobao.org2.打包构建生产环境npm run build:prod构建打包成功之后,会在根目录生成 dist 文件夹,里面就是构建打包好的文件,通常是 ***.js 、***.css、index.html 等静态文件。3.然后将dist文件夹的静态文件上传到服务器4.进入nginx安装目录,修改nginx.conf文件cd /usr/local/nginx/confvim nginx.confhttp下新增下列内容,并修改相关配置server { listen 89; //端口 server_name 192.168.XX.XX; //ip charset utf-8; location / { root /home/dataplatform/dist; //dist文件存放目录 index index.html index.htm; try_files $uri $uri/ /index.html; } location /api/{ proxy_pass http://192.168.XX.XX:8080/; //后端服务器访问地址 } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; }}5.重新启动nginxcd /usr/local/nginx/sbin./nginx /usr/local/nginx/conf/nginx.conf6)登录7)效果图 \ No newline at end of file diff --git a/metaops.iml b/metaops.iml new file mode 100644 index 0000000..08c1eba --- /dev/null +++ b/metaops.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..371a6ee --- /dev/null +++ b/pom.xml @@ -0,0 +1,259 @@ + + + 4.0.0 + + com.salpa + salpa + 3.8.3 + + metaops + 敏捷元数据模型设计系统 + + + 3.8.3 + UTF-8 + UTF-8 + 11 + 3.1.1 + 1.2.11 + 1.21 + 3.0.0 + 2.3.2 + 2.2.2 + 1.4.1 + 2.0.8 + 6.1.6 + 2.11.0 + 1.4 + 3.2.2 + 4.1.2 + 2.3 + 0.9.1 + + + + + + + + + org.springframework.boot + spring-boot-dependencies + 2.5.14 + pom + import + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + eu.bitwalker + UserAgentUtils + ${bitwalker.version} + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + + io.springfox + springfox-boot-starter + ${swagger.version} + + + io.swagger + swagger-models + + + + + + + commons-io + commons-io + ${commons.io.version} + + + + + commons-fileupload + commons-fileupload + ${commons.fileupload.version} + + + + org.apache.commons + commons-compress + 1.21 + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + com.deepoove + poi-tl + 1.10.0 + + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + + commons-collections + commons-collections + ${commons.collections.version} + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + + com.github.penggle + kaptcha + ${kaptcha.version} + + + + + com.salpa + salpa-quartz + ${metaops.version} + + + + + com.salpa + salpa-generator + ${metaops.version} + + + + + com.salpa + salpa-framework + ${metaops.version} + + + + + com.salpa + salpa-system + ${metaops.version} + + + + + com.salpa + salpa-common + ${metaops.version} + + + + + + + salpa-admin + salpa-framework + salpa-system + salpa-quartz + salpa-generator + salpa-common + + pom + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + false + + + + + \ No newline at end of file diff --git a/salpa-admin/pom.xml b/salpa-admin/pom.xml new file mode 100644 index 0000000..1544b2c --- /dev/null +++ b/salpa-admin/pom.xml @@ -0,0 +1,277 @@ + + + + salpa + com.salpa + 3.8.3 + + 4.0.0 + jar + salpa-admin + + + web服务入口 + + + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + io.springfox + springfox-boot-starter + + + + + io.swagger + swagger-models + 1.6.2 + + + + + mysql + mysql-connector-java + + + + + com.oracle + ojdbc6 + 11.2.0.3 + + + + + org.postgresql + postgresql + 42.3.1 + + + + + org.mariadb.jdbc + mariadb-java-client + 2.7.3 + + + + + + + + + + + + + + + com.microsoft.sqlserver + mssql-jdbc + 10.2.1.jre11 + + + + + com.clickhouse + clickhouse-jdbc + 0.3.2-patch9 + + + + + org.elasticsearch.plugin + x-pack-sql-jdbc + 7.10.0 + + + + + + + + org.apache.kylin + kylin-jdbc + 2.6.3 + + + + + org.apache.kyuubi + kyuubi-hive-jdbc-shaded + 1.6.0-incubating + + + + + io.prestosql + presto-jdbc + 350 + + + + + io.trino + trino-jdbc + 397 + + + + + org.apache.hive + hive-jdbc + 1.2.1 + + + org.glassfish + javax.el + + + jdk.tools + jdk.tools + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.slf4j + slf4j-log4j12 + + + ch.qos.logback + logback-classic + + + org.eclipse.jetty.orbit + * + + + org.eclipse.jetty.aggregate + * + + + tomcat + * + + + javax.servlet + servlet-api + + + org.mortbay.jetty + * + + + + + + + com.alibaba + druid + 1.2.8 + + + + + + com.dameng + DmJdbcDriver18 + 8.1.2.79 + + + + + + + + + + com.aliyun.odps + odps-jdbc + 1.6 + + + + + + org.xerial + sqlite-jdbc + 3.36.0.3 + + + + + com.salpa + salpa-framework + + + + + com.salpa + salpa-quartz + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.1.RELEASE + + true + + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 + + false + ${project.artifactId} + + + + ${project.artifactId} + + + \ No newline at end of file diff --git a/salpa-admin/salpa-admin.iml b/salpa-admin/salpa-admin.iml new file mode 100644 index 0000000..8424d40 --- /dev/null +++ b/salpa-admin/salpa-admin.iml @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salpa-admin/src/main/java/com/salpa/SalpaApplication.java b/salpa-admin/src/main/java/com/salpa/SalpaApplication.java new file mode 100644 index 0000000..3edc4e8 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/SalpaApplication.java @@ -0,0 +1,23 @@ +package com.salpa; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 启动程序 + * + * @author salpa + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +@EnableScheduling +public class SalpaApplication +{ + public static void main(String[] args) + { + // System.setProperty("spring.devtools.restart.enabled", "false"); + SpringApplication.run(SalpaApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 敏捷元数据模型设计后台启动成功 ლ(´ڡ`ლ)゙ "); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/SalpaServletInitializer.java b/salpa-admin/src/main/java/com/salpa/SalpaServletInitializer.java new file mode 100644 index 0000000..6d919d1 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/SalpaServletInitializer.java @@ -0,0 +1,18 @@ +package com.salpa; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author salpa + */ +public class SalpaServletInitializer extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(SalpaApplication.class); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/common/CaptchaController.java b/salpa-admin/src/main/java/com/salpa/web/controller/common/CaptchaController.java new file mode 100644 index 0000000..be698c6 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/common/CaptchaController.java @@ -0,0 +1,94 @@ +package com.salpa.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.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.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.CacheConstants; +import com.salpa.common.constant.Constants; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.redis.RedisCache; +import com.salpa.common.utils.sign.Base64; +import com.salpa.common.utils.uuid.IdUtils; +import com.salpa.system.service.ISysConfigService; + +/** + * 验证码操作处理 + * + * @author salpa + */ +@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 captchaOnOff = configService.selectCaptchaOnOff(); + ajax.put("captchaOnOff", captchaOnOff); + if (!captchaOnOff) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + // 生成验证码 + String captchaType = SalpaConfig.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/salpa-admin/src/main/java/com/salpa/web/controller/common/CommonController.java b/salpa-admin/src/main/java/com/salpa/web/controller/common/CommonController.java new file mode 100644 index 0000000..a2a9677 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/common/CommonController.java @@ -0,0 +1,163 @@ +package com.salpa.web.controller.common; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.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.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.Constants; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.file.FileUploadUtils; +import com.salpa.common.utils.file.FileUtils; +import com.salpa.framework.config.ServerConfig; + +/** + * 通用请求处理 + * + * @author salpa + */ +@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 = SalpaConfig.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 = SalpaConfig.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 files) throws Exception + { + try + { + // 上传文件路径 + String filePath = SalpaConfig.getUploadPath(); + List urls = new ArrayList(); + List fileNames = new ArrayList(); + List newFileNames = new ArrayList(); + List originalFilenames = new ArrayList(); + 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 = SalpaConfig.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/salpa-admin/src/main/java/com/salpa/web/controller/common/MockApiContoller.java b/salpa-admin/src/main/java/com/salpa/web/controller/common/MockApiContoller.java new file mode 100644 index 0000000..0923cd6 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/common/MockApiContoller.java @@ -0,0 +1,542 @@ +package com.salpa.web.controller.common; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.utils.DateUtils; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MockApiContoller { + String[] titles = new String[] { + "Alipay", + "Angular", + "Ant Design", + "Ant Design Pro", + "Bootstrap", + "React", + "Vue", + "Webpack", + }; + String[] avatars = new String[] { + "https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png", // Alipay + "https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png", // Angular + "https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png", // Ant Design + "https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png", // Ant Design Pro + "https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png", // Bootstrap + "https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png", // React + "https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png", // Vue + "https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png", // Webpack + }; + + String[] covers = new String[] { + "https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png", + "https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png", + "https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png", + "https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png", + }; + String[] desc = new String[] { + "那是一种内在的东西, 他们到达不了,也无法触及的", + "希望是一个好东西,也许是最好的,好东西是不会消亡的", + "生命就像一盒巧克力,结果往往出人意料", + "城镇中有那么多的酒馆,她却偏偏走进了我的酒馆", + "那时候我只会想自己想要什么,从不想自己拥有什么", + }; + + String[] user = new String[] { + "付小小", + "曲丽丽", + "林东东", + "周星星", + "吴加好", + "朱偏右", + "鱼酱", + "乐哥", + "谭小仪", + "仲尼", + }; + @GetMapping("/fake_list_Detail") + public AjaxResult getFakeListDetail() { + JSONArray info = new JSONArray(); + for(int i = 0 ; i < 20 ; i ++) { + JSONObject item = new JSONObject(); + item.put("id", "fake-list-" + i); + item.put("owner", user[i % 10]); + item.put("title", titles[i % 8]); + item.put("avatar", avatars[i % 8]); + item.put("cover", covers[i % 4]); + item.put("status", "active"); + item.put("percent", Math.ceil(Math.random() * 50) + 50); + item.put("logo", avatars[i % 8]); + item.put("href", "https://ant.design"); + item.put("description", "在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。"); + item.put("content", "段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。"); + item.put("activeUser", Math.ceil(Math.random() * 100000) + 100000); + item.put("newUser", Math.ceil(Math.random() * 1000) + 1000); + item.put("star", Math.ceil(Math.random() * 100) + 100); + item.put("like", Math.ceil(Math.random() * 100) + 100); + item.put("message", Math.ceil(Math.random() * 10) + 10); + JSONArray members = new JSONArray(); + item.put("members", members); + info.add(item); + } + + JSONObject list = new JSONObject(); + list.put("list", info); + AjaxResult ajax = AjaxResult.success(); + ajax.put("data", list); + return ajax; + } + + @GetMapping("/notices") + public AjaxResult getNoticeData() { + JSONArray info = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("id", "000000012"); + item.put("avatar", avatars[0]); + item.put("title", "ABCD 版本发布"); + item.put("description", "提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务"); + item.put("extra", "进行中"); + item.put("status", "processing"); + item.put("type", "event"); + info.add(item); + + item = new JSONObject(); + item.put("id", "000000006"); + item.put("avatar", avatars[1]); + item.put("title", "左侧图标用于区分不同的类型"); + item.put("datetime", "2022-01-07"); + item.put("type", "notification"); + info.add(item); + + item = new JSONObject(); + item.put("id", "000000006"); + item.put("avatar", avatars[2]); + item.put("title", "曲丽丽 评论了你"); + item.put("description", "描述信息描述信息描述信息"); + item.put("datetime", "2022-01-01"); + item.put("type", "message"); + item.put("clickClose", true); + info.add(item); + + AjaxResult ajax = AjaxResult.success(); + ajax.put("data", info); + return ajax; + } + + @GetMapping("/tags") + public AjaxResult getTags() { + JSONArray taglist = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("name", "离岛"); + item.put("type", "1"); + item.put("value", "19"); + taglist.add(item); + + item = new JSONObject(); + item.put("name", "大同市"); + item.put("type", "1"); + item.put("value", "68"); + taglist.add(item); + + item = new JSONObject(); + item.put("name", "三亚市"); + item.put("type", "0"); + item.put("value", "42"); + taglist.add(item); + + JSONObject list = new JSONObject(); + list.put("list", taglist); + AjaxResult ajax = AjaxResult.success(); + ajax.put("data", list); + return ajax; + } + + @GetMapping("/project/notice") + public AjaxResult getProjectNotice() { + JSONArray info = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("id", "xxx1"); + item.put("logo", avatars[0]); + item.put("title", "Alipay"); + item.put("description", "那是一种内在的东西,他们到达不了,也无法触及的"); + item.put("updatedAt", DateUtils.getTime()); + item.put("member", "科学搬砖组"); + item.put("href", ""); + item.put("memberLink", ""); + info.add(item); + + item = new JSONObject(); + item.put("id", "xxx2"); + item.put("logo", avatars[1]); + item.put("title", "Angular"); + item.put("description", "希望是一个好东西,也许是最好的,好东西是不会消亡的"); + item.put("updatedAt", DateUtils.getTime()); + item.put("member", "全组都是吴彦祖"); + item.put("href", ""); + item.put("memberLink", ""); + info.add(item); + + item = new JSONObject(); + item.put("id", "xxx3"); + item.put("logo", avatars[2]); + item.put("title", "Ant Design"); + item.put("description", "城镇中有那么多的酒馆,她却偏偏走进了我的酒馆"); + item.put("updatedAt", DateUtils.getTime()); + item.put("member", "中二少女团"); + item.put("href", ""); + item.put("memberLink", ""); + info.add(item); + + item = new JSONObject(); + item.put("id", "xxx4"); + item.put("logo", avatars[3]); + item.put("title", "Bootstrap"); + item.put("description", "那时候我只会想自己想要什么,从不想自己拥有什么"); + item.put("updatedAt", DateUtils.getTime()); + item.put("member", "程序员日常"); + item.put("href", ""); + item.put("memberLink", ""); + info.add(item); + + item = new JSONObject(); + item.put("id", "xxx5"); + item.put("logo", avatars[4]); + item.put("title", "React"); + item.put("description", "凛冬将至"); + item.put("updatedAt", DateUtils.getTime()); + item.put("member", "高逼格设计天团"); + item.put("href", ""); + item.put("memberLink", ""); + info.add(item); + + item = new JSONObject(); + item.put("id", "xxx6"); + item.put("logo", avatars[5]); + item.put("title", "Webpack"); + item.put("description", "生命就像一盒巧克力,结果往往出人意料"); + item.put("updatedAt", DateUtils.getTime()); + item.put("member", "程序员日常"); + item.put("href", ""); + item.put("memberLink", ""); + info.add(item); + + AjaxResult ajax = AjaxResult.success(); + ajax.put("data", info); + return ajax; + } + + @GetMapping("/activities") + public AjaxResult getActivities() { + JSONArray list = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("id", "trend-1"); + item.put("updatedAt", DateUtils.getTime()); + JSONObject user = new JSONObject(); + user.put("name", "曲丽丽"); + user.put("avatar", avatars[2]); + item.put("user", user); + JSONObject group = new JSONObject(); + user.put("name", "高逼格设计天团"); + user.put("link", "http://github.com/"); + item.put("group", group); + JSONObject project = new JSONObject(); + user.put("name", "六月迭代"); + user.put("link", "http://github.com/"); + item.put("project", project); + item.put("template", "在 @{group} 新建项目 @{project}"); + list.add(item); + + item = new JSONObject(); + item.put("id", "trend-2"); + item.put("updatedAt", DateUtils.getTime()); + user = new JSONObject(); + user.put("name", "付小小"); + user.put("avatar", avatars[1]); + item.put("user", user); + group = new JSONObject(); + user.put("name", "高逼格设计天团"); + user.put("link", "http://github.com/"); + item.put("group", group); + project = new JSONObject(); + user.put("name", "六月迭代"); + user.put("link", "http://github.com/"); + item.put("project", project); + item.put("template", "在 @{group} 新建项目 @{project}"); + list.add(item); + + item = new JSONObject(); + item.put("id", "trend-3"); + item.put("updatedAt", DateUtils.getTime()); + user = new JSONObject(); + user.put("name", "林东东"); + user.put("avatar", avatars[3]); + item.put("user", user); + group = new JSONObject(); + user.put("name", "中二少女团"); + user.put("link", "http://github.com/"); + item.put("group", group); + project = new JSONObject(); + user.put("name", "六月迭代"); + user.put("link", "http://github.com/"); + item.put("project", project); + item.put("template", "在 @{group} 新建项目 @{project}"); + list.add(item); + + AjaxResult ajax = AjaxResult.success(); + ajax.put("data", list); + return ajax; + } + + @GetMapping("/fake_workplace_chart_data") + public AjaxResult getWorkplaceChartData() { + return getChartData("work"); + } + + @GetMapping("/fake_analysis_chart_data") + public AjaxResult getAnalysisChartData() { + return getChartData("analysis"); + } + + public AjaxResult getChartData(String type) { + JSONArray visitData = new JSONArray(); + int[] fakeY = { 7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5 }; + for (int i = 0; i < fakeY.length; i += 1) { + JSONObject item = new JSONObject(); + Date d = new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * i); + item.put("x", DateUtils.dateTime(d)); + item.put("y", fakeY[i]); + visitData.add(item); + } + + JSONArray visitData2 = new JSONArray(); + int[] fakeY2 = { 1, 6, 4, 8, 3, 7, 2 }; + for (int i = 0; i < fakeY2.length; i += 1) { + JSONObject item = new JSONObject(); + Date d = new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * i); + item.put("x", DateUtils.dateTime(d)); + item.put("y", fakeY2[i]); + visitData2.add(item); + } + + JSONArray salesData = new JSONArray(); + for (Integer i = 1; i < 13; i += 1) { + JSONObject item = new JSONObject(); + item.put("x", i.toString() + "月"); + item.put("y", Math.floor(Math.random() * 1000 + 200)); + salesData.add(item); + } + + JSONArray searchData = new JSONArray(); + for (Integer i = 0; i < 50; i += 1) { + JSONObject item = new JSONObject(); + item.put("index", i + 1); + item.put("keyword", "搜索关键词-" + i.toString()); + item.put("count", Math.floor(Math.random() * 1000)); + item.put("range", Math.floor(Math.random() * 1000)); + item.put("status", Math.floor(Math.random() * 1000)); + searchData.add(item); + } + + JSONArray offlineData = new JSONArray(); + for (Integer i = 1; i <= 50; i += 1) { + JSONObject item = new JSONObject(); + item.put("name", "Stores " + i.toString()); + item.put("cvr", Math.ceil(Math.random() * 9 / 10)); + offlineData.add(item); + } + + JSONArray offlineChartData = new JSONArray(); + if (type.equals("work")) { + for (Integer i = 1; i <= 50; i += 1) { + JSONObject item = new JSONObject(); + item.put("x", new Date().getTime() + 1000 * 60 * 60 * i); + item.put("y1", Math.floor(Math.random() * 1000) + 10); + item.put("y2", Math.floor(Math.random() * 1000) + 10); + offlineChartData.add(item); + } + } else { + for (Integer i = 1; i <= 20; i += 1) { + JSONObject item = new JSONObject(); + String date = DateUtils.parseDateToStr("HH:mm", new Date(new Date().getTime() + 1000 * 60 * 60 * i)); + item.put("date", date); + item.put("type", "客流量"); + item.put("value", Math.floor(Math.random() * 100) + 10); + + item = new JSONObject(); + item.put("date", date); + item.put("type", "支付笔数"); + item.put("value", Math.floor(Math.random() * 100) + 10); + offlineChartData.add(item); + } + } + JSONArray salesTypeData = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("x", "家用电器"); + item.put("y", 4544); + salesTypeData.add(item); + + item = new JSONObject(); + item.put("x", "食用酒水"); + item.put("y", 3321); + salesTypeData.add(item); + + item = new JSONObject(); + item.put("x", "个护健康"); + item.put("y", 3113); + salesTypeData.add(item); + + item = new JSONObject(); + item.put("x", "服饰箱包"); + item.put("y", 2341); + salesTypeData.add(item); + + item = new JSONObject(); + item.put("x", "母婴产品"); + item.put("y", 1231); + salesTypeData.add(item); + + item = new JSONObject(); + item.put("x", "其他"); + item.put("y", 1231); + salesTypeData.add(item); + + JSONArray salesTypeDataOnline = new JSONArray(); + item = new JSONObject(); + item.put("x", "家用电器"); + item.put("y", 244); + salesTypeDataOnline.add(item); + + item = new JSONObject(); + item.put("x", "食用酒水"); + item.put("y", 321); + salesTypeDataOnline.add(item); + + item = new JSONObject(); + item.put("x", "家用电器"); + item.put("y", 244); + salesTypeDataOnline.add(item); + + item = new JSONObject(); + item.put("x", "个护健康"); + item.put("y", 311); + salesTypeDataOnline.add(item); + + item = new JSONObject(); + item.put("x", "服饰箱包"); + item.put("y", 41); + salesTypeDataOnline.add(item); + + item = new JSONObject(); + item.put("x", "母婴产品"); + item.put("y", 121); + salesTypeDataOnline.add(item); + + item = new JSONObject(); + item.put("x", "其他"); + item.put("y", 111); + salesTypeDataOnline.add(item); + + JSONArray salesTypeDataOffline = new JSONArray(); + item = new JSONObject(); + item.put("x", "家用电器"); + item.put("y", 99); + salesTypeDataOffline.add(item); + + item = new JSONObject(); + item.put("x", "食用酒水"); + item.put("y", 188); + salesTypeDataOffline.add(item); + + item = new JSONObject(); + item.put("x", "家用电器"); + item.put("y", 244); + salesTypeDataOffline.add(item); + + item = new JSONObject(); + item.put("x", "个护健康"); + item.put("y", 311); + salesTypeDataOffline.add(item); + + item = new JSONObject(); + item.put("x", "服饰箱包"); + item.put("y", 66); + salesTypeDataOffline.add(item); + + item = new JSONObject(); + item.put("x", "母婴产品"); + item.put("y", 46); + salesTypeDataOffline.add(item); + + item = new JSONObject(); + item.put("x", "其他"); + item.put("y", 65); + salesTypeDataOffline.add(item); + + JSONArray radarOriginData = new JSONArray(); + if (type.equals("work")) { + + ArrayList radarTypes = new ArrayList<>(); + radarTypes.add("个人"); + radarTypes.add("团队"); + radarTypes.add("部门"); + + Map radarTitleMap = new HashMap<>(); + radarTitleMap.put("ref", "引用"); + radarTitleMap.put("koubei", "口碑"); + radarTitleMap.put("output", "产量"); + radarTitleMap.put("contribute", "贡献"); + radarTitleMap.put("hot", "热度"); + + for (String radarType : radarTypes) { + for (Entry key : radarTitleMap.entrySet()) { + item = new JSONObject(); + item.put("name", radarType); + item.put("label", key.getValue()); + item.put("value", Math.floor(Math.random() * 10)); + radarOriginData.add(item); + } + } + + radarOriginData.add(item); + } else { + item = new JSONObject(); + item.put("x", "家用电器"); + item.put("y", 99); + radarOriginData.add(item); + + item = new JSONObject(); + item.put("x", "食用酒水"); + item.put("y", 188); + radarOriginData.add(item); + + item = new JSONObject(); + item.put("x", "家用电器"); + item.put("y", 244); + radarOriginData.add(item); + } + + JSONObject res = new JSONObject(); + res.put("visitData", visitData); + res.put("visitData2", visitData2); + res.put("salesData", salesData); + res.put("searchData", searchData); + res.put("offlineData", offlineData); + res.put("offlineChartData", offlineChartData); + res.put("salesTypeData", salesTypeData); + res.put("salesTypeDataOnline", salesTypeDataOnline); + res.put("salesTypeDataOffline", salesTypeDataOffline); + res.put("radarData", radarOriginData); + AjaxResult ajax = AjaxResult.success(); + ajax.put("data", res); + return ajax; + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/model/ModelController.java b/salpa-admin/src/main/java/com/salpa/web/controller/model/ModelController.java new file mode 100644 index 0000000..6846d41 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/model/ModelController.java @@ -0,0 +1,134 @@ +package com.salpa.web.controller.model; + +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.model.domain.ProjectModelInfo; +import com.salpa.model.service.ModelService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 模型管理 + * + * @author zhuff + */ +@RestController +@RequestMapping("/model/management") +public class ModelController extends BaseController { + + @Autowired + private ModelService modelService; + + /** + * 获取当前用户项目模型列表 + * @author zhuff + * @return + */ + @GetMapping("list") + public TableDataInfo getList(@RequestParam Integer pageNum,@RequestParam Integer pageSize,@RequestParam String projectName) { + List list = modelService.getList(pageNum,pageSize,projectName); + return getDataTable(list); + } + + /** + * 根据项目id获取项目模型 + * @author zhuff + * @param projectId + * @return + */ + @GetMapping("getById") + public AjaxResult getById(Integer projectId) { + return AjaxResult.success(modelService.getById(projectId)); + } + + /** + * 新增项目模型 + * @author zhuff + * @param projectModelInfo + * @return + */ + @PostMapping("add") + public AjaxResult addProject(@RequestBody ProjectModelInfo projectModelInfo) throws IOException { + if (UserConstants.NOT_UNIQUE.equals(modelService.checkProjectNameUnique(projectModelInfo.getProjectName()))) + { + return AjaxResult.error("新增项目模型'" + projectModelInfo.getProjectName() + "'失败,该项目模型已存在"); + } + return toAjax(modelService.addProject(projectModelInfo)); + } + + /** + * 修改项目模型 + * @author zhuff + * @param projectModelInfo + * @return + */ + @PostMapping("update") + public AjaxResult updateProject(@RequestBody ProjectModelInfo projectModelInfo) { + ProjectModelInfo project = modelService.getById(projectModelInfo.getProjectId()); + if (!project.getProjectName().equals(projectModelInfo.getProjectName())){ + if (UserConstants.NOT_UNIQUE.equals(modelService.checkProjectNameUnique(projectModelInfo.getProjectName()))) + { + return AjaxResult.error("修改项目模型'" + projectModelInfo.getProjectName() + "'失败,该项目模型已存在"); + } + } + return toAjax(modelService.updateProject(projectModelInfo)); + } + + /** + * 删除项目模型 + * @author zhuff + * @param projectId + * @return + */ + @DeleteMapping("delete") + public AjaxResult deleteProject(Integer projectId) { + return toAjax(modelService.deleteProject(projectId)); + } + + /** + * 获取除管理员外所有用户 + * @author zhuff + * @return + */ + @GetMapping("userList") + public AjaxResult getUserList() { + List> list = modelService.getUserList(); + return AjaxResult.success(list); + } + + /** + * 更新jsonFile + * @param projectModelInfo + * @return + */ + @PostMapping("updateJsonFile") + public AjaxResult updateJsonFile(@RequestBody ProjectModelInfo projectModelInfo){ + return toAjax(modelService.updateJsonFile(projectModelInfo)); + } + + /** + * 测试连接数据库 + * @param map + * @return + */ + @PostMapping("connectDataSource") + public AjaxResult connectDataSource(@RequestBody Map map) { + return modelService.connectDataSource(map); + } + + /** + * 执行ddl语句 + * @param map + * @return + */ + @PostMapping("executeSql") + public AjaxResult executeSql(@RequestBody Map map) { + return modelService.executeSql(map); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/monitor/CacheController.java b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/CacheController.java new file mode 100644 index 0000000..9c8a45d --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/CacheController.java @@ -0,0 +1,119 @@ +package com.salpa.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 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.salpa.common.constant.CacheConstants; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.domain.SysCache; + +/** + * 缓存监控 + * + * @author salpa + */ +@RestController +@RequestMapping("/monitor/cache") +public class CacheController +{ + @Autowired + private RedisTemplate redisTemplate; + + private final static List caches = new ArrayList(); + { + 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, "限流处理")); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Properties info = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info()); + Properties commandStats = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) connection -> connection.dbSize()); + + Map result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map 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 cacheKyes = redisTemplate.keys(cacheName + "*"); + return AjaxResult.success(cacheKyes); + } + + @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 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 cacheKeys = redisTemplate.keys("*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/monitor/ServerController.java b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/ServerController.java new file mode 100644 index 0000000..cb23b3b --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/ServerController.java @@ -0,0 +1,27 @@ +package com.salpa.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.salpa.common.core.domain.AjaxResult; +import com.salpa.framework.web.domain.Server; + +/** + * 服务器监控 + * + * @author salpa + */ +@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/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysLogininforController.java b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysLogininforController.java new file mode 100644 index 0000000..23a36c8 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,69 @@ +package com.salpa.web.controller.monitor; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.system.domain.SysLogininfor; +import com.salpa.system.service.ISysLogininforService; + +/** + * 系统访问记录 + * + * @author salpa + */ +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController +{ + @Autowired + private ISysLogininforService logininforService; + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) + { + startPage(); + List 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 list = logininforService.selectLogininforList(logininfor); + ExcelUtil util = new ExcelUtil(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 AjaxResult.success(); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysOperlogController.java b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysOperlogController.java new file mode 100644 index 0000000..f688af8 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,69 @@ +package com.salpa.web.controller.monitor; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.system.domain.SysOperLog; +import com.salpa.system.service.ISysOperLogService; + +/** + * 操作日志记录 + * + * @author salpa + */ +@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 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 list = operLogService.selectOperLogList(operLog); + ExcelUtil util = new ExcelUtil(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 AjaxResult.success(); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysUserOnlineController.java b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 0000000..46fff02 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,92 @@ +package com.salpa.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.salpa.common.annotation.Log; +import com.salpa.common.constant.CacheConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.core.redis.RedisCache; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.domain.SysUserOnline; +import com.salpa.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author salpa + */ +@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 keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + List userOnlineList = new ArrayList(); + for (String key : keys) + { + LoginUser user = redisCache.getCacheObject(key); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) + { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) + { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } + } + else if (StringUtils.isNotEmpty(ipaddr)) + { + if (StringUtils.equals(ipaddr, user.getIpaddr())) + { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } + } + else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) + { + if (StringUtils.equals(userName, user.getUsername())) + { + 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 AjaxResult.success(); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/online/OnlineController.java b/salpa-admin/src/main/java/com/salpa/web/controller/online/OnlineController.java new file mode 100644 index 0000000..abcf497 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/online/OnlineController.java @@ -0,0 +1,58 @@ +package com.salpa.web.controller.online; + +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.utils.StringUtils; +import com.salpa.online.service.OnlineService; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * @author zhuff + * + * 数据库在线查询 + */ +@RestController +@RequestMapping("/online/demand") +public class OnlineController { + + @Autowired + private OnlineService onlineService; + + /** + * 获取数据源 + * @return + */ + @PostMapping("getDataSource") + public AjaxResult getDataSource() { + return AjaxResult.success(onlineService.getDataSource()); + } + + /** + * 连接数据库 + * @param databaseName + * @return + */ + @GetMapping("connectDataSource") + public AjaxResult connectDataSource(String databaseName) { + return onlineService.connectDataSource(databaseName); + } + + /** + * 执行 + * @param databaseName + * @param script + * @return + */ + @GetMapping("testExecute") + public AjaxResult testExecute(String databaseName,String script) { + if (StringUtils.isEmpty(databaseName)) { + return AjaxResult.error("请连接数据源"); + } else { + return onlineService.testExecute(databaseName,script); + } + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/subject/DatabaseTypeController.java b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DatabaseTypeController.java new file mode 100644 index 0000000..a1c10df --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DatabaseTypeController.java @@ -0,0 +1,171 @@ +package com.salpa.web.controller.subject; + +import com.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.utils.file.FileUploadUtils; +import com.salpa.subject.domain.MonitorDatabaseType; +import com.salpa.subject.service.DatabaseTypeService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * 数据库类型 + * + * @author zhuff + */ +@RestController +@RequestMapping("/subject/databaseType") +public class DatabaseTypeController extends BaseController { + + @Autowired + private DatabaseTypeService databaseTypeService; + + /** + * 获取数据库类型列表 + * @author zhuff + * @return + */ + @GetMapping("getDatabaseType") + public AjaxResult getDatabaseTypeAll() { + List list = databaseTypeService.getDatabaseTypeAll(); + return AjaxResult.success(list); + } + + /** + * 数据库类型列表查询 + * @author zhuff + * @return + */ + @GetMapping("list") + public AjaxResult getList(String databaseType) { + List list = databaseTypeService.getList(databaseType); + return AjaxResult.success(list); + } + + /** + * 创建数据库类型 + * @param monitorDatabaseType + * @return + */ + @PostMapping("add") + public AjaxResult addDatabaseType(@RequestBody MonitorDatabaseType monitorDatabaseType) { + if (monitorDatabaseType.getUrlPattern() == null) { + return AjaxResult.error("url pattern 不能为空"); + } + if (!monitorDatabaseType.getUrlPattern().contains("{{jdbc.protocol}}")) { + return AjaxResult.error("必须包含变量{{jdbc.protocol}}"); + } + if (!monitorDatabaseType.getUrlPattern().contains("{{db.url}}")) { + return AjaxResult.error("必须包含变量{{db.url}}不能为空"); + } + if (!monitorDatabaseType.getUrlPattern().contains("{{db.schema}}") && !monitorDatabaseType.getUrlPattern().contains("{{db.name}}")) { + return AjaxResult.error("{{db.schema}} 和 {{db.name}} 至少设置一个"); + } + if (UserConstants.NOT_UNIQUE.equals(databaseTypeService.checkDatabaseTypeUnique(monitorDatabaseType.getDatabaseType()))) { + return AjaxResult.error("该数据库类型已存在"); + } + return toAjax(databaseTypeService.addDatabaseType(monitorDatabaseType)); + } + + /** + * 修改数据库类型 + * @param monitorDatabaseType + * @return + */ + @PostMapping("update") + public AjaxResult updateDatabaseType(@RequestBody MonitorDatabaseType monitorDatabaseType) { + if (monitorDatabaseType.getUrlPattern() == null) { + return AjaxResult.error("url pattern 不能为空"); + } + if (!monitorDatabaseType.getUrlPattern().contains("{{jdbc.protocol}}")) { + return AjaxResult.error("必须包含变量{{jdbc.protocol}}"); + } + if (!monitorDatabaseType.getUrlPattern().contains("{{db.url}}")) { + return AjaxResult.error("必须包含变量{{db.url}}不能为空"); + } + /*if (!monitorDatabaseType.getUrlPattern().contains("{{db.schema}}") && !monitorDatabaseType.getUrlPattern().contains("{{db.name}}")) { + return AjaxResult.error("{{db.schema}} 和 {{db.name}} 至少设置一个"); + }*/ + if (!databaseTypeService.getById(monitorDatabaseType.getId()).getDatabaseType().equals(monitorDatabaseType.getDatabaseType())) { + if (UserConstants.NOT_UNIQUE.equals(databaseTypeService.checkDatabaseTypeUnique(monitorDatabaseType.getDatabaseType()))) { + return AjaxResult.error("该数据库类型已存在"); + } + } + return toAjax(databaseTypeService.updateDatabaseType(monitorDatabaseType)); + } + + /** + * 删除数据库类型 + * @param id + * @return + */ + @DeleteMapping("delete") + public AjaxResult deleteDatabaseType(Integer id) { + return toAjax(databaseTypeService.deleteDatabaseType(id)); + } + + /** + * 解析数据库驱动类名 + * @param jdbcDriverFileUrl + * @return + */ + @GetMapping("resolveDriverClassName") + public AjaxResult resolveDriverClassName(String jdbcDriverFileUrl,String jdbcDriverFilePath) { + if (StringUtils.isAllBlank(jdbcDriverFilePath, jdbcDriverFileUrl)) { + return AjaxResult.error("请填写下载驱动的地址或手动上传驱动文件"); + } else if (StringUtils.isNotBlank(jdbcDriverFilePath)) { + File saveDir = new File(SalpaConfig.getProfile()+jdbcDriverFilePath); + if (!saveDir.exists()) { + return AjaxResult.error("驱动文件不存在,请重新上传"); + } else { + String className = databaseTypeService.resolveDriverClassNameFromLocal(jdbcDriverFilePath); + if (className == "") { + return AjaxResult.error("解析失败,请检查地址"); + } else { + return AjaxResult.success(className); + } + } + } else if (StringUtils.isNotBlank(jdbcDriverFileUrl)) { + String className = databaseTypeService.resolveDriverClassName(jdbcDriverFileUrl); + if (className == "") { + return AjaxResult.error("解析失败,请检查地址"); + } else { + return AjaxResult.success(className); + } + } else { + return AjaxResult.error("解析失败,请检查地址"); + } + } + + /** + * 上传数据库驱动 + * @param file + * @return + */ + @PostMapping("uploadDriver") + public AjaxResult uploadDriver(MultipartFile file) { + String fileName = ""; + try { + // 上传驱动文件路径 + String filePath = SalpaConfig.getDriverPath(); + File file1 = new File(filePath); + if (file1.exists()) { + file1.delete(); + } + // 上传并返回新文件名称 + fileName = FileUploadUtils.upload(filePath, file); + } catch (IOException e) { + return AjaxResult.error("上传失败,请检查后重新上传"); + } + return AjaxResult.success(fileName); + } + +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentController.java b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentController.java new file mode 100644 index 0000000..d67be98 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentController.java @@ -0,0 +1,156 @@ +package com.salpa.web.controller.subject; + +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.subject.service.DocumentService; +import com.salpa.subject.service.ProjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + + +/** + * 数据库文档 + * + * @author zhuff + */ +@RestController +@RequestMapping("/subject/document") +public class DocumentController extends BaseController { + + @Autowired + private DocumentService documentService; + + @Autowired + private ProjectService projectService; + + /** + * 创建同步任务 + * @param projectId + * @return + */ + @GetMapping("createSyncTask") + public AjaxResult createSyncTask(Integer projectId) { + LoginUser loginUser = SecurityUtils.getLoginUser(); + int syncTask = documentService.createSyncTask(projectId, loginUser.getUserId(), false); + if (syncTask > 0) { + return AjaxResult.success(syncTask); + } else { + return AjaxResult.error("创建同步任务失败"); + } + } + + /** + * 获取版本号 + * @param projectId + * @return + */ + @GetMapping("getVersionsByProjectId") + public AjaxResult getVersionsByProjectId(Integer projectId) { + return AjaxResult.success(documentService.getVersionsByProjectId(projectId)); + } + + /** + * 获取文档信息 + * @param projectId + * @param version + * @param search + * @param originalVersion + * @return + */ + @GetMapping("getSimpleByProjectId") + public AjaxResult getSimpleByProjectId(Integer projectId,Long version,String search,Long originalVersion) { + return AjaxResult.success(documentService.getSimpleByProjectId(projectId,version,search,originalVersion)); + } + + /** + * 获取表名列表 + * @param projectId + * @param version + * @param search + * @return + */ + @GetMapping("getTableDocumentByProjectId") + public AjaxResult getTableDocumentByProjectId(Integer projectId,Long version,String search){ + return AjaxResult.success(documentService.getTableDocumentByProjectId(projectId,version,search)); + } + + /** + * 获取每张表详细信息 + * @param id + * @param projectId + * @param version + * @param originalVersion + * @return + */ + @GetMapping("getTableDetail") + public AjaxResult getTableDetail(Integer id,Integer projectId,Long version,Long originalVersion) { + return AjaxResult.success(documentService.getTableDetail(id,projectId,version,originalVersion)); + } + + /** + * 获取同步任务列表 + * @param taskIds + * @return + */ + @GetMapping("getSyncTaskList") + public AjaxResult getSyncTaskList(@RequestParam Integer[] taskIds) { + return AjaxResult.success(documentService.getSyncTaskList(taskIds)); + } + + /** + * 取消同步任务 + * @param taskId + * @return + */ + @GetMapping("cancelTask") + public AjaxResult cancelTask(Integer taskId) { + return toAjax(documentService.cancelTask(taskId)); + } + + /** + * 获取同步任务状态 + * @param taskId + * @return + */ + @GetMapping("getSyncTaskStatus") + public AjaxResult getSyncTaskStatus(Integer taskId) { + return AjaxResult.success(documentService.getSyncTaskStatus(taskId)); + } + + /** + * 导出文档 + * @param projectId + * @param version + * @param fileType + * @return + */ + @GetMapping("getDocumentFiles") + public ResponseEntity getDocumentFiles(Integer projectId, Long version, String fileType) { + HttpHeaders headers = new HttpHeaders(); + String projectName = projectService.getProjectById(projectId).getProjectName(); + String fileName = projectName; + if ("Markdown".equals(fileType)) { + fileName += ".md"; + } else if ("UML SVG".equals(fileType)) { + fileName += ".svg"; + } else if ("UML PNG".equals(fileType)) { + fileName += ".png"; + } else if ("Word".equals(fileType)) { + fileName += ".docx"; + } + headers.setContentDisposition(ContentDisposition.attachment() + .filename(fileName) + .build()); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + return ResponseEntity.ok() + .headers(headers) + .body(out -> documentService.export(projectId, version, fileType, out)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentDescriptionController.java b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentDescriptionController.java new file mode 100644 index 0000000..a8cb5d8 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentDescriptionController.java @@ -0,0 +1,35 @@ +package com.salpa.web.controller.subject; + +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.subject.domain.MonitorDocumentDescription; +import com.salpa.subject.service.DocumentDescriptionService; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/subject/documentDescription") +public class DocumentDescriptionController extends BaseController { + + @Autowired + private DocumentDescriptionService documentDescriptionService; + + /** + * 修改描述 + * @author rhl + * @param monitorDocumentDescription + * @return + */ + @PostMapping("update") + public AjaxResult updateDescription(@RequestBody MonitorDocumentDescription monitorDocumentDescription){ + SysUser user = SecurityUtils.getLoginUser().getUser(); + monitorDocumentDescription.setUpdateBy(user.getUserId()); + System.out.println(monitorDocumentDescription); + return AjaxResult.success(documentDescriptionService.updateDescription(monitorDocumentDescription)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentDiscussionController.java b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentDiscussionController.java new file mode 100644 index 0000000..9a70afc --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentDiscussionController.java @@ -0,0 +1,55 @@ +package com.salpa.web.controller.subject; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.subject.domain.MonitorDocumentDiscussion; +import com.salpa.subject.service.DocumentDiscussionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 评论 + * @author + */ +@RestController +@RequestMapping("/subject/documentDiscussion") +public class DocumentDiscussionController extends BaseController { + @Autowired + private DocumentDiscussionService documentDiscussionService; + /** + * 添加评论 + * @author rhl + * @param monitorDocumentDiscussion + * @return + */ + @PostMapping("add") + public AjaxResult addDiscussion(@RequestBody MonitorDocumentDiscussion monitorDocumentDiscussion) { + SysUser user = SecurityUtils.getLoginUser().getUser(); + monitorDocumentDiscussion.setUserId(user.getUserId()); + return AjaxResult.success(documentDiscussionService.add(monitorDocumentDiscussion)); + } + /** + * 获取评论列表 + * @author hzl + * @return + */ + @PostMapping ("list") + public TableDataInfo getList(@RequestBody MonitorDocumentDiscussion monitorDocumentDiscussion) { + List list = documentDiscussionService.getList(monitorDocumentDiscussion); + return getDataTable(list); + } + /** + * 删除评论 + * @author hzl + * @param id + * @return + */ + @DeleteMapping("delete") + public AjaxResult deleteComment(Integer id){ + return toAjax(documentDiscussionService.deleteComment(id)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentTemplateController.java b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentTemplateController.java new file mode 100644 index 0000000..ec75700 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/subject/DocumentTemplateController.java @@ -0,0 +1,30 @@ +package com.salpa.web.controller.subject; + +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.subject.service.DocumentTemplateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 文档模板 + * + * @author zhuff + */ +@RestController +@RequestMapping("/subject/documentTemplate") +public class DocumentTemplateController { + + @Autowired + private DocumentTemplateService documentTemplateService; + + /** + * 获取模板属性 + * @return + */ + @PostMapping("getAllProperties") + public AjaxResult getAllProperties() { + return AjaxResult.success(documentTemplateService.getAllProperties()); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/subject/ProjectController.java b/salpa-admin/src/main/java/com/salpa/web/controller/subject/ProjectController.java new file mode 100644 index 0000000..cc2e2b7 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/subject/ProjectController.java @@ -0,0 +1,154 @@ +package com.salpa.web.controller.subject; + +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.subject.domain.MonitorOperationLog; +import com.salpa.subject.domain.MonitorProject; +import com.salpa.subject.domain.vo.MonitorDataSourceVo; +import com.salpa.subject.domain.vo.OperationLogVo; +import com.salpa.subject.service.ProjectService; +import org.quartz.CronExpression; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.text.ParseException; +import java.util.List; +import java.util.Map; + +/** + * 项目管理 + * + * @author zhuff + */ +@RestController +@RequestMapping("/subject/project") +public class ProjectController extends BaseController { + + @Autowired + private ProjectService projectService; + + /** + * 获取项目列表 + * @author zhuff + * @param monitorDataSourceVo + * @return + */ + @PostMapping("list") + public TableDataInfo getList(@RequestBody MonitorDataSourceVo monitorDataSourceVo) { + List> list = projectService.getList(monitorDataSourceVo); + return getDataTable(list); + } + + /** + * 创建项目 + * @author zhuff + * @param monitorProject + * @return + */ + @PostMapping("createProject") + public AjaxResult createProject(@RequestBody MonitorProject monitorProject) { + if (monitorProject.getProjectSyncRule().isAutoSync()){ + try { + new CronExpression(monitorProject.getProjectSyncRule().getAutoSyncCron()); + } catch (ParseException e) { + e.printStackTrace(); + return AjaxResult.error("新增项目模型'" + monitorProject.getProjectName() + "'失败,错误的Cron表达式"); + } + } + if (UserConstants.NOT_UNIQUE.equals(projectService.checkProjectNameUnique(monitorProject.getProjectName()))) + { + return AjaxResult.error("新增项目模型'" + monitorProject.getProjectName() + "'失败,该项目模型已存在"); + } + if (UserConstants.NOT_UNIQUE.equals(projectService.checkDatabaseNameUnique(monitorProject.getMonitorDataSource().getDatabaseName()))) + { + return AjaxResult.error("新增数据库'" + monitorProject.getMonitorDataSource().getDatabaseName() + "'失败,该数据库已存在"); + } + return toAjax(projectService.addProject(monitorProject)); + } + + /** + * 修改项目 + * @author zhuff + * @param monitorProject + * @return + */ + @PostMapping("updateProject") + public AjaxResult updateProject(@RequestBody MonitorProject monitorProject) { + if (monitorProject.getProjectSyncRule().isAutoSync()){ + try { + new CronExpression(monitorProject.getProjectSyncRule().getAutoSyncCron()); + } catch (ParseException e) { + e.printStackTrace(); + return AjaxResult.error("修改项目模型'" + monitorProject.getProjectName() + "'失败,错误的Cron表达式"); + } + } + if (!projectService.getById(monitorProject.getId()).getProjectName().equals(monitorProject.getProjectName())) { + if (UserConstants.NOT_UNIQUE.equals(projectService.checkProjectNameUnique(monitorProject.getProjectName()))) + { + return AjaxResult.error("修改项目模型'" + monitorProject.getProjectName() + "'失败,该项目模型已存在"); + } + } + if (!projectService.getDataSource(monitorProject.getId()).getDatabaseName().equals(monitorProject.getMonitorDataSource().getDatabaseName())) { + if (UserConstants.NOT_UNIQUE.equals(projectService.checkDatabaseNameUnique(monitorProject.getMonitorDataSource().getDatabaseName()))) + { + return AjaxResult.error("修改数据库'" + monitorProject.getMonitorDataSource().getDatabaseName() + "'失败,该数据库已存在"); + } + } + return toAjax(projectService.updateProject(monitorProject)); + } + + /** + * 根据id获取项目 + * @author zhuff + * @param id + * @return + */ + @GetMapping("getProjectById") + public AjaxResult getProjectById(@RequestParam Integer id) { + return AjaxResult.success(projectService.getProjectById(id)); + } + + /** + * 删除项目 + * @param id + * @return + */ + @DeleteMapping("deleteProject") + public AjaxResult deleteProject(Integer id){ + return toAjax(projectService.deleteProject(id)); + } + + + /** + * 测试连接 + * @param monitorProject + * @return + */ + @PostMapping("testConnection") + public AjaxResult testConnection(@RequestBody MonitorProject monitorProject){ + int i = projectService.testConnection(monitorProject.getMonitorDataSource()); + if (i == 1) { + return AjaxResult.error("加载驱动失败,请检查后重试"); + } else if (i == 2) { + return AjaxResult.error("数据库连接失败,请检查后重试"); + } else if (i == 3) { + return AjaxResult.success("连接数据库成功"); + } else { + return AjaxResult.error("连接失败,请检查后重试"); + } + } + + /** + * 获取日志列表 + * @param operationLogVo + * @return + */ + @PostMapping("getLogList") + public TableDataInfo getLogList(@RequestBody OperationLogVo operationLogVo) { + List list = projectService.getLogList(operationLogVo); + return getDataTable(list); + } + +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/subject/SubjectController.java b/salpa-admin/src/main/java/com/salpa/web/controller/subject/SubjectController.java new file mode 100644 index 0000000..ed7931d --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/subject/SubjectController.java @@ -0,0 +1,94 @@ +package com.salpa.web.controller.subject; + +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.subject.domain.MonitorSubject; +import com.salpa.subject.service.SubjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 主题管理 + * + * @author zhuff + */ +@RestController +@RequestMapping("/subject/manage") +public class SubjectController extends BaseController { + + @Autowired + private SubjectService subjectService; + + /** + * 获取主题管理列表 + * @author zhuff + * @param pageNum + * @param pageSize + * @param subjectName + * @return + */ + @GetMapping("list") + public TableDataInfo getList(@RequestParam Integer pageNum,@RequestParam Integer pageSize,@RequestParam String subjectName) { + List list = subjectService.getList(pageNum,pageSize,subjectName); + return getDataTable(list); + } + + /** + * 根据项目id获取主题管理 + * @author zhuff + * @param id + * @return + */ + @GetMapping("getById") + public AjaxResult getById(Integer id) { + return AjaxResult.success(subjectService.getById(id)); + } + + /** + * 新增主题管理 + * @author zhuff + * @param monitorSubject + * @return + */ + @PostMapping("add") + public AjaxResult addSubject(@RequestBody MonitorSubject monitorSubject) { + if (UserConstants.NOT_UNIQUE.equals(subjectService.checkSubjectNameUnique(monitorSubject.getSubjectName()))) + { + return AjaxResult.error("新增主题'" + monitorSubject.getSubjectName() + "'失败,该主题已存在"); + } + return toAjax(subjectService.addSubject(monitorSubject)); + } + + /** + * 修改主题管理 + * @author zhuff + * @param monitorSubject + * @return + */ + @PostMapping("update") + public AjaxResult updateSubject(@RequestBody MonitorSubject monitorSubject) { + MonitorSubject subject = subjectService.getById(monitorSubject.getId()); + if (!subject.getSubjectName().equals(monitorSubject.getSubjectName())){ + if (UserConstants.NOT_UNIQUE.equals(subjectService.checkSubjectNameUnique(monitorSubject.getSubjectName()))) + { + return AjaxResult.error("修改主题'" + monitorSubject.getSubjectName() + "'失败,该主题已存在"); + } + } + return toAjax(subjectService.updateSubject(monitorSubject)); + } + + /** + * 删除主题管理 + * @author zhuff + * @param id + * @return + */ + @DeleteMapping("delete") + public AjaxResult deleteSubject(Integer id) { + return toAjax(subjectService.deleteSubject(id)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/subject/WarningController.java b/salpa-admin/src/main/java/com/salpa/web/controller/subject/WarningController.java new file mode 100644 index 0000000..0f4fb23 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/subject/WarningController.java @@ -0,0 +1,47 @@ +package com.salpa.web.controller.subject; + +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.subject.domain.MonitorWarning; +import com.salpa.subject.service.WarningService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 告警管理 + * @author zff + */ +@RestController +@RequestMapping("/subject/warning") +public class WarningController extends BaseController { + + @Autowired + private WarningService warningService; + + /** + * 获取告警列表 + * @param pageNum 页数 + * @param pageSize 条数 + * @return list + */ + @GetMapping("list") + public TableDataInfo warningList(Integer pageNum,Integer pageSize) { + List list = warningService.warningList(pageNum,pageSize); + return getDataTable(list); + } + + /** + * 修改告警状态 + * @param id 告警id + * @return 结果 + */ + @GetMapping("updateStatus") + public AjaxResult updateStatus(Long id) { + return toAjax(warningService.updateStatus(id)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysConfigController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysConfigController.java new file mode 100644 index 0000000..50dc5a0 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysConfigController.java @@ -0,0 +1,134 @@ +package com.salpa.web.controller.system; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.system.domain.SysConfig; +import com.salpa.system.service.ISysConfigService; + +/** + * 参数配置 信息操作处理 + * + * @author salpa + */ +@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 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 list = configService.selectConfigList(config); + ExcelUtil util = new ExcelUtil(SysConfig.class); + util.exportExcel(response, list, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return AjaxResult.success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return AjaxResult.success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:add')") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) + { + if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) + { + return AjaxResult.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 (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) + { + return AjaxResult.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 AjaxResult.success(); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDeptController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDeptController.java new file mode 100644 index 0000000..b752856 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDeptController.java @@ -0,0 +1,165 @@ +package com.salpa.web.controller.system; + +import java.util.Iterator; +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.salpa.common.annotation.Log; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysDept; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.service.ISysDeptService; + +/** + * 部门信息 + * + * @author salpa + */ +@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 depts = deptService.selectDeptList(dept); + return AjaxResult.success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) + { + List depts = deptService.selectDeptList(new SysDept()); + Iterator it = depts.iterator(); + while (it.hasNext()) + { + SysDept d = (SysDept) it.next(); + if (d.getDeptId().intValue() == deptId + || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")) + { + it.remove(); + } + } + return AjaxResult.success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable Long deptId) + { + deptService.checkDeptDataScope(deptId); + return AjaxResult.success(deptService.selectDeptById(deptId)); + } + + /** + * 获取部门下拉树列表 + */ + @GetMapping("/treeselect") + public AjaxResult treeselect(SysDept dept) + { + List depts = deptService.selectDeptList(dept); + return AjaxResult.success(deptService.buildDeptTreeSelect(depts)); + } + + /** + * 加载对应角色部门列表树 + */ + @GetMapping(value = "/roleDeptTreeselect/{roleId}") + public AjaxResult roleDeptTreeselect(@PathVariable("roleId") Long roleId) + { + List depts = deptService.selectDeptList(new SysDept()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.buildDeptTreeSelect(depts)); + return ajax; + } + + /** + * 新增部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:add')") + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) + { + if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) + { + return AjaxResult.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 (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) + { + return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + else if (dept.getParentId().equals(deptId)) + { + return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } + else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0) + { + return AjaxResult.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 AjaxResult.error("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) + { + return AjaxResult.error("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return toAjax(deptService.deleteDeptById(deptId)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDictDataController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDictDataController.java new file mode 100644 index 0000000..ec71dc6 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDictDataController.java @@ -0,0 +1,121 @@ +package com.salpa.web.controller.system; + +import java.util.ArrayList; +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysDictData; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.system.service.ISysDictDataService; +import com.salpa.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author salpa + */ +@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 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 list = dictDataService.selectDictDataList(dictData); + ExcelUtil util = new ExcelUtil(SysDictData.class); + util.exportExcel(response, list, "字典数据"); + } + + /** + * 查询字典数据详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable Long dictCode) + { + return AjaxResult.success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable String dictType) + { + List data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) + { + data = new ArrayList(); + } + return AjaxResult.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/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDictTypeController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDictTypeController.java new file mode 100644 index 0000000..b097c7b --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysDictTypeController.java @@ -0,0 +1,132 @@ +package com.salpa.web.controller.system; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysDictType; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author salpa + */ +@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 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 list = dictTypeService.selectDictTypeList(dictType); + ExcelUtil util = new ExcelUtil(SysDictType.class); + util.exportExcel(response, list, "字典类型"); + } + + /** + * 查询字典类型详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable Long dictId) + { + return AjaxResult.success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) + { + if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict))) + { + return AjaxResult.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 (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict))) + { + return AjaxResult.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 AjaxResult.success(); + } + + /** + * 获取字典选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List dictTypes = dictTypeService.selectDictTypeAll(); + return AjaxResult.success(dictTypes); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysIndexController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysIndexController.java new file mode 100644 index 0000000..be82000 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysIndexController.java @@ -0,0 +1,29 @@ +package com.salpa.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.salpa.common.config.SalpaConfig; +import com.salpa.common.utils.StringUtils; + +/** + * 首页 + * + * @author salpa + */ +@RestController +public class SysIndexController +{ + /** 系统基础配置 */ + @Autowired + private SalpaConfig salpaConfig; + + /** + * 访问首页,提示语 + */ + @RequestMapping("/") + public String index() + { + return StringUtils.format("欢迎使用{}敏捷元数据模型后台,当前版本:v{},请通过前端地址访问。", salpaConfig.getName(), salpaConfig.getVersion()); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysLoginController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysLoginController.java new file mode 100644 index 0000000..68e5476 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysLoginController.java @@ -0,0 +1,86 @@ +package com.salpa.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.salpa.common.constant.Constants; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysMenu; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.domain.model.LoginBody; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.framework.web.service.SysLoginService; +import com.salpa.framework.web.service.SysPermissionService; +import com.salpa.system.service.ISysMenuService; + +/** + * 登录验证 + * + * @author salpa + */ +@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 roles = permissionService.getRolePermission(user); + // 权限集合 + Set 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 menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysMenuController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysMenuController.java new file mode 100644 index 0000000..f3c2e2f --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysMenuController.java @@ -0,0 +1,142 @@ +package com.salpa.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.salpa.common.annotation.Log; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysMenu; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.service.ISysMenuService; + +/** + * 菜单信息 + * + * @author salpa + */ +@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 menus = menuService.selectMenuList(menu, getUserId()); + return AjaxResult.success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable Long menuId) + { + return AjaxResult.success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return AjaxResult.success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) + { + List 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 (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) + { + return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return AjaxResult.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 (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) + { + return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (menu.getMenuId().equals(menu.getParentId())) + { + return AjaxResult.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 AjaxResult.error("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) + { + return AjaxResult.error("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysNoticeController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysNoticeController.java new file mode 100644 index 0000000..15a3bc6 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysNoticeController.java @@ -0,0 +1,91 @@ +package com.salpa.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.salpa.common.annotation.Log; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.system.domain.SysNotice; +import com.salpa.system.service.ISysNoticeService; + +/** + * 公告 信息操作处理 + * + * @author salpa + */ +@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 list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable Long noticeId) + { + return AjaxResult.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/salpa-admin/src/main/java/com/salpa/web/controller/system/SysPostController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysPostController.java new file mode 100644 index 0000000..4f04b10 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysPostController.java @@ -0,0 +1,130 @@ +package com.salpa.web.controller.system; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.system.domain.SysPost; +import com.salpa.system.service.ISysPostService; + +/** + * 岗位信息操作处理 + * + * @author salpa + */ +@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 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 list = postService.selectPostList(post); + ExcelUtil util = new ExcelUtil(SysPost.class); + util.exportExcel(response, list, "岗位数据"); + } + + /** + * 根据岗位编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable Long postId) + { + return AjaxResult.success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:add')") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) + { + if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post))) + { + return AjaxResult.error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) + { + return AjaxResult.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 (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post))) + { + return AjaxResult.error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) + { + return AjaxResult.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 posts = postService.selectPostAll(); + return AjaxResult.success(posts); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysProfileController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysProfileController.java new file mode 100644 index 0000000..d7aabbf --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysProfileController.java @@ -0,0 +1,142 @@ +package com.salpa.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.salpa.common.annotation.Log; +import com.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.file.FileUploadUtils; +import com.salpa.common.utils.file.MimeTypeUtils; +import com.salpa.framework.web.service.TokenService; +import com.salpa.system.service.ISysUserService; + +/** + * 个人信息 业务处理 + * + * @author salpa + */ +@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 sysUser = loginUser.getUser(); + user.setUserName(sysUser.getUserName()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUserId(sysUser.getUserId()); + user.setPassword(null); + if (userService.updateUserProfile(user) > 0) + { + // 更新缓存用户信息 + sysUser.setNickName(user.getNickName()); + sysUser.setPhonenumber(user.getPhonenumber()); + sysUser.setEmail(user.getEmail()); + sysUser.setSex(user.getSex()); + tokenService.setLoginUser(loginUser); + return AjaxResult.success(); + } + return AjaxResult.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 AjaxResult.error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) + { + return AjaxResult.error("新密码不能与旧密码相同"); + } + if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) + { + // 更新缓存用户密码 + loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword)); + tokenService.setLoginUser(loginUser); + return AjaxResult.success(); + } + return AjaxResult.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 = "/profile"+ FileUploadUtils.upload(SalpaConfig.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 AjaxResult.error("上传图片异常,请联系管理员"); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysRegisterController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysRegisterController.java new file mode 100644 index 0000000..fd008f7 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysRegisterController.java @@ -0,0 +1,38 @@ +package com.salpa.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.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.model.RegisterBody; +import com.salpa.common.utils.StringUtils; +import com.salpa.framework.web.service.SysRegisterService; +import com.salpa.system.service.ISysConfigService; + +/** + * 注册验证 + * + * @author salpa + */ +@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/salpa-admin/src/main/java/com/salpa/web/controller/system/SysRoleController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysRoleController.java new file mode 100644 index 0000000..f1d9f78 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysRoleController.java @@ -0,0 +1,245 @@ +package com.salpa.web.controller.system; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.framework.web.service.SysPermissionService; +import com.salpa.framework.web.service.TokenService; +import com.salpa.system.domain.SysUserRole; +import com.salpa.system.service.ISysRoleService; +import com.salpa.system.service.ISysUserService; + +/** + * 角色信息 + * + * @author salpa + */ +@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; + + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/list") + public TableDataInfo list(SysRole role) + { + startPage(); + List 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 list = roleService.selectRoleList(role); + ExcelUtil util = new ExcelUtil(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 AjaxResult.success(roleService.selectRoleById(roleId)); + } + + /** + * 新增角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:add')") + @Log(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysRole role) + { + if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) + { + return AjaxResult.error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) + { + return AjaxResult.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 (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) + { + return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) + { + return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setUpdateBy(getUsername()); + + if (roleService.updateRole(role) > 0) + { + // 更新缓存用户权限 + LoginUser loginUser = getLoginUser(); + if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) + { + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); + tokenService.setLoginUser(loginUser); + } + return AjaxResult.success(); + } + return AjaxResult.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 AjaxResult.success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) + { + startPage(); + List list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) + { + startPage(); + List 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)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/system/SysUserController.java b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysUserController.java new file mode 100644 index 0000000..346e15d --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/system/SysUserController.java @@ -0,0 +1,237 @@ +package com.salpa.web.controller.system; + +import java.util.List; +import java.util.stream.Collectors; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.system.service.ISysPostService; +import com.salpa.system.service.ISysRoleService; +import com.salpa.system.service.ISysUserService; + +/** + * 用户信息 + * + * @author salpa + */ +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysPostService postService; + + /** + * 获取用户列表 + */ + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/list") + public TableDataInfo list(SysUser user) + { + startPage(); + List 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 list = userService.selectUserList(user); + ExcelUtil util = new ExcelUtil(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 util = new ExcelUtil(SysUser.class); + List userList = util.importExcel(file.getInputStream()); + String operName = getUsername(); + String message = userService.importUser(userList, updateSupport, operName); + return AjaxResult.success(message); + } + + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil util = new ExcelUtil(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 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) + { + if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserName()))) + { + return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return AjaxResult.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()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return AjaxResult.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 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); + userService.insertUserAuth(userId, roleIds); + return success(); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/tool/SwaggerController.java b/salpa-admin/src/main/java/com/salpa/web/controller/tool/SwaggerController.java new file mode 100644 index 0000000..c1bc38e --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/tool/SwaggerController.java @@ -0,0 +1,24 @@ +package com.salpa.web.controller.tool; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import com.salpa.common.core.controller.BaseController; + +/** + * swagger 接口 + * + * @author salpa + */ +@Controller +@RequestMapping("/tool/swagger") +public class SwaggerController extends BaseController +{ + @PreAuthorize("@ss.hasPermi('tool:swagger:view')") + @GetMapping() + public String index() + { + return redirect("/swagger-ui.html"); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/controller/tool/TestController.java b/salpa-admin/src/main/java/com/salpa/web/controller/tool/TestController.java new file mode 100644 index 0000000..33cf14e --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/controller/tool/TestController.java @@ -0,0 +1,183 @@ +package com.salpa.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.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.R; +import com.salpa.common.utils.StringUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; + +/** + * swagger 用户测试方法 + * + * @author salpa + */ +@Api("用户信息管理") +@RestController +@RequestMapping("/test/user") +public class TestController extends BaseController +{ + private final static Map users = new LinkedHashMap(); + { + users.put(1, new UserEntity(1, "admin", "admin123", "15888888888")); + users.put(2, new UserEntity(2, "ry", "admin123", "15666666666")); + } + + @ApiOperation("获取用户列表") + @GetMapping("/list") + public R> userList() + { + List userList = new ArrayList(users.values()); + return R.ok(userList); + } + + @ApiOperation("获取用户详细") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @GetMapping("/{userId}") + public R getUser(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + return R.ok(users.get(userId)); + } + else + { + return R.fail("用户不存在"); + } + } + + @ApiOperation("新增用户") + @ApiImplicitParams({ + @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class), + @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class) + }) + @PostMapping("/save") + public R save(UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + users.put(user.getUserId(), user); + return R.ok(); + } + + @ApiOperation("更新用户") + @PutMapping("/update") + public R 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(); + } + + @ApiOperation("删除用户信息") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @DeleteMapping("/{userId}") + public R delete(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + users.remove(userId); + return R.ok(); + } + else + { + return R.fail("用户不存在"); + } + } +} + +@ApiModel(value = "UserEntity", description = "用户实体") +class UserEntity +{ + @ApiModelProperty("用户ID") + private Integer userId; + + @ApiModelProperty("用户名称") + private String username; + + @ApiModelProperty("用户密码") + private String password; + + @ApiModelProperty("用户手机") + 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/salpa-admin/src/main/java/com/salpa/web/core/config/SwaggerConfig.java b/salpa-admin/src/main/java/com/salpa/web/core/config/SwaggerConfig.java new file mode 100644 index 0000000..4965bbe --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/core/config/SwaggerConfig.java @@ -0,0 +1,125 @@ +package com.salpa.web.core.config; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.salpa.common.config.SalpaConfig; +import io.swagger.annotations.ApiOperation; +import io.swagger.models.auth.In; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; + +/** + * Swagger2的接口配置 + * + * @author salpa + */ +@Configuration +public class SwaggerConfig +{ + /** 系统基础配置 */ + @Autowired + private SalpaConfig salpaConfig; + + /** 是否开启swagger */ + @Value("${swagger.enabled}") + private boolean enabled; + + /** 设置请求的统一前缀 */ + @Value("${swagger.pathMapping}") + private String pathMapping; + + /** + * 创建API + */ + @Bean + public Docket createRestApi() + { + return new Docket(DocumentationType.OAS_30) + // 是否启用Swagger + .enable(enabled) + // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) + .apiInfo(apiInfo()) + // 设置哪些接口暴露给Swagger展示 + .select() + // 扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + // 扫描指定包中的swagger注解 + // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) + // 扫描所有 .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build() + /* 设置安全模式,swagger可以设置访问token */ + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List securitySchemes() + { + List apiKeyList = new ArrayList(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List securityContexts() + { + List securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + /** + * 默认的安全上引用 + */ + private List defaultAuth() + { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + /** + * 添加摘要信息 + */ + private ApiInfo apiInfo() + { + // 用ApiInfoBuilder进行定制 + return new ApiInfoBuilder() + // 设置标题 + .title("标题:数据模型统一管理系统_接口文档") + // 描述 + .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") + // 作者信息 + .contact(new Contact(salpaConfig.getName(), null, null)) + // 版本 + .version("版本号:" + salpaConfig.getVersion()) + .build(); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncJob.java b/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncJob.java new file mode 100644 index 0000000..94e05ab --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncJob.java @@ -0,0 +1,26 @@ +package com.salpa.web.job; + +import com.salpa.subject.service.DocumentService; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; + +public class ProjectSyncJob implements Job { + + @Autowired + private DocumentService documentService; + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + JobDataMap dataMap = context.getMergedJobDataMap(); + Integer projectId = dataMap.getInt("projectId"); + try { + documentService.createSyncTask(projectId, (long)-1, true); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncJobScheduler.java b/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncJobScheduler.java new file mode 100644 index 0000000..5ac0091 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncJobScheduler.java @@ -0,0 +1,180 @@ +package com.salpa.web.job; + +import com.salpa.subject.domain.MonitorProjectSyncRule; +import com.salpa.subject.mapper.ProjectSyncRuleMapper; +import com.salpa.subject.service.DocumentService; +import org.quartz.*; +import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.util.Pair; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 用于启用 / 停用项目同步任务的调度器 + */ +@Component +public class ProjectSyncJobScheduler { + + private static final String JOB_IDENTITY_PATTERN = "JOB_PROJECT[%s]"; + + private static final String TRIGGER_IDENTITY_PATTERN = "TRIGGER_PROJECT[%s]"; + + @Autowired + private Scheduler scheduler; + + @Autowired + private ProjectSyncRuleMapper projectSyncRuleMapper; + + @Autowired + private DocumentService documentService; + + /** + * 启动 isAutoSync=true 的任务 + */ + @Scheduled(fixedRate = 1000) + public void scheduleProjectSync() { + List list = getProjectJobDetails(); + for (JobDetail jobDetail : list) { + System.out.println(jobDetail.getJobDataMap().getInt("projectId")); + System.out.println(jobDetail.getJobDataMap().get("projectId")); + } + List currentlyTriggerProjectIds = getProjectJobDetails() + .stream() + .map(job -> job.getJobDataMap().getInt("projectId")) + .collect(Collectors.toList()); + // 查询已开启自动同步但还未运行的任务 + projectSyncRuleMapper.selectByIsAutoSyncAndNotInProjectIds(true, currentlyTriggerProjectIds) + .stream() + .map(this::jobAndTriggers) + .forEach(jobAndTriggers -> { + try { + if (!scheduler.checkExists(jobAndTriggers.getSecond().getKey())) { + scheduler.scheduleJob(jobAndTriggers.getFirst(), jobAndTriggers.getSecond()); + } + } catch (SchedulerException e) { + e.printStackTrace(); + } + }); + } + + /** + * 停止 Rule.cron 更新过的任务 + */ + @Scheduled(fixedRate = 1000) + public void reScheduleProjectSync() { + List list = getProjectJobDetails(); + for (JobDetail jobDetail : list) { + System.out.println(jobDetail.getJobDataMap().getInt("projectId")); + System.out.println(jobDetail.getJobDataMap().get("projectId")); + } + List triggerKeys = getProjectJobDetails() + .stream() + .filter(jobDetail -> { + JobDataMap dataMap = jobDetail.getJobDataMap(); + System.out.println(dataMap.getInt("projectId")); + int projectId = dataMap.getInt("projectId"); + String cron = dataMap.getString("cron"); + return !projectSyncRuleMapper.existsByProjectIdAndCron(projectId, cron); + }) + .map(jobDetail -> jobDetail.getJobDataMap().getInt("projectId")) + .map(this::triggerKey) + .collect(Collectors.toList()); + try { + if (!triggerKeys.isEmpty()) { + scheduler.unscheduleJobs(triggerKeys); + } + } catch (SchedulerException e) { + e.printStackTrace(); + } + } + + /** + * 停止 isAutoSync=false 的项目 + */ + @Scheduled(fixedRate = 1000) + public void unScheduleProjectSync() { + List list = getProjectJobDetails(); + for (JobDetail jobDetail : list) { + System.out.println(jobDetail.getJobDataMap().getInt("projectId")); + System.out.println(jobDetail.getJobDataMap().get("projectId")); + } + List projectIds = getProjectJobDetails() + .stream() + .map(job -> job.getJobDataMap().getInt("projectId")) + .collect(Collectors.toList()); + // 查询已关闭自动同步但还在运行的任务 + List triggerKeys = projectSyncRuleMapper.selectByIsAutoSyncAndProjectIds(false, projectIds) + .stream() + .map(p -> triggerKey(p.getProjectId())) + .collect(Collectors.toList()); + try { + if (!triggerKeys.isEmpty()) { + scheduler.unscheduleJobs(triggerKeys); + } + } catch (SchedulerException e) { + e.printStackTrace(); + } + } + + private List getProjectJobDetails() { + try { + return scheduler.getJobKeys(GroupMatcher.anyGroup()) + .stream() + .map(jobKey -> { + try { + JobDetail job = scheduler.getJobDetail(jobKey); + if (job.getJobDataMap().containsKey("projectId")) { + return Optional.of(job); + } else { + return Optional.empty(); + } + } catch (SchedulerException e) { + return Optional.empty(); + } + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } catch (SchedulerException e) { + return Collections.emptyList(); + } + } + + private Pair jobAndTriggers(MonitorProjectSyncRule rule) { + JobDataMap dataMap = new JobDataMap(); + Integer projectId = rule.getProjectId(); + Integer ruleId = rule.getId(); + dataMap.put("projectId", projectId); + dataMap.put("ruleId", ruleId); + dataMap.put("cron", rule.getAutoSyncCron()); + dataMap.put("documentService", documentService); + JobDetail job = JobBuilder.newJob() + .ofType(com.salpa.web.job.ProjectSyncJob.class) + .withIdentity(jobKey(projectId)) + .withDescription("auto sync project document") + .usingJobData(dataMap) + .build(); + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(rule.getAutoSyncCron()); + Trigger trigger = TriggerBuilder.newTrigger() + .withIdentity(triggerKey(projectId)) + .forJob(job) + .withSchedule(scheduleBuilder) + .startNow() + .build(); + return Pair.of(job, trigger); + } + + private TriggerKey triggerKey(Integer projectId) { + return TriggerKey.triggerKey(String.format(TRIGGER_IDENTITY_PATTERN, projectId)); + } + + private JobKey jobKey(Integer projectId) { + return JobKey.jobKey(String.format(JOB_IDENTITY_PATTERN, projectId)); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncTaskScheduler.java b/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncTaskScheduler.java new file mode 100644 index 0000000..cd00691 --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/job/ProjectSyncTaskScheduler.java @@ -0,0 +1,118 @@ +package com.salpa.web.job; + +import com.alibaba.fastjson2.JSON; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.subject.domain.MonitorOperationLog; +import com.salpa.subject.domain.MonitorProjectSyncTask; +import com.salpa.subject.domain.data.ProjectSyncTaskStatus; +import com.salpa.subject.mapper.OperationLogMapper; +import com.salpa.subject.mapper.ProjectSyncTaskMapper; +import com.salpa.subject.service.DocumentSyncService; +import com.salpa.system.mapper.SysUserMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; + +@Component +@RequiredArgsConstructor +@Slf4j +public class ProjectSyncTaskScheduler { + + @Autowired + private DocumentSyncService documentSyncService; + + @Autowired + private OperationLogMapper operationLogMapper; + + @Autowired + private ProjectSyncTaskMapper projectSyncTaskMapper; + + @Autowired + private SysUserMapper sysUserMapper; + + private final ThreadPoolTaskExecutor projectSyncTaskThreadPoolTaskExecutor; + + + /*每隔 5s 执行一次*/ + @Scheduled(fixedRate = 5000L) + public void startSyncTask() { + final int size = 10; + List tasks = projectSyncTaskMapper.listNewTasks(size); + tasks.forEach(task -> { + projectSyncTaskThreadPoolTaskExecutor.execute(() -> { + Integer taskId = task.getId(); + Integer projectId = task.getProjectId(); + Long userId = task.getUserId(); + sync(taskId, projectId, userId); + }); + }); + } + + private void sync(Integer taskId, Integer projectId, Long userId) { + try { + updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.RUNNING.toString(), "running"); + Integer docId = documentSyncService.syncByProjectId(projectId, taskId); + String status = documentSyncService.selectStatusById(taskId); + if (!"CANCELED".equals(status)) { + updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.FINISHED.toString(), "ok"); + saveOperationLog(projectId, userId, null); + } else { + if (docId != null) { + Long version = documentSyncService.getDocumentVersionById(docId); + documentSyncService.deleteDocumentById(docId); + documentSyncService.updateDocument(projectId,version-1); + } + } + } catch (Exception e) { + String result = Objects.requireNonNullElse(e.getMessage(), "unknown"); + updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.FAILED.toString(), result); + saveOperationLog(projectId, userId, e); + throw e; + } + } + + private void updateSyncTaskStatus(Integer taskId, String status, String result) { + projectSyncTaskMapper.updateStatusAndResultById(taskId, status, result); + } + + private void saveOperationLog(Integer projectId, Long userId, Exception ex) { + String operatorNickName; + String operatorUsername; + String operationName; + if (Objects.equals(-1, userId)) { + operatorNickName = "system"; + operatorUsername = "system"; + operationName = "定时同步"; + } else { + SysUser user = sysUserMapper.selectUserById(userId); + operatorNickName = user.getNickName(); + operatorUsername = user.getUserName(); + operationName = "手动同步"; + } + AjaxResult response; + if (ex == null) { + response = AjaxResult.success(); + } else { + response = AjaxResult.error(-1,ex.getMessage()); + } + MonitorOperationLog operationLog = MonitorOperationLog.builder() + .operatorNickname(operatorNickName) + .operatorUsername(operatorUsername) + .operatorUserId(userId) + .operationName(operationName) + .operationCode("autoSyncDocumentation") + .operationModule("project") + .operationResponse(JSON.toJSONString(response)) + .success(ex == null) + .involvedProjectId(projectId) + .build(); + operationLogMapper.addOperationLog(operationLog); + } +} diff --git a/salpa-admin/src/main/java/com/salpa/web/job/config/ProjectSyncTaskThreadPoolConfig.java b/salpa-admin/src/main/java/com/salpa/web/job/config/ProjectSyncTaskThreadPoolConfig.java new file mode 100644 index 0000000..879b65e --- /dev/null +++ b/salpa-admin/src/main/java/com/salpa/web/job/config/ProjectSyncTaskThreadPoolConfig.java @@ -0,0 +1,27 @@ +package com.salpa.web.job.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +public class ProjectSyncTaskThreadPoolConfig { + + @Bean + public ThreadPoolTaskExecutor projectSyncTaskThreadPoolTaskExecutor() { + final int maxCorePoolSize = 12; + final int maxPoolSize = 32; + + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + int availableProcessorCount = Runtime.getRuntime().availableProcessors() + 2; + int corePoolSize = Math.min(maxCorePoolSize, availableProcessorCount); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setAllowCoreThreadTimeOut(true); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); + return executor; + } + +} diff --git a/salpa-admin/src/main/resources/.idea/inspectionProfiles/Project_Default.xml b/salpa-admin/src/main/resources/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..6560a98 --- /dev/null +++ b/salpa-admin/src/main/resources/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/salpa-admin/src/main/resources/.idea/misc.xml b/salpa-admin/src/main/resources/.idea/misc.xml new file mode 100644 index 0000000..fe90235 --- /dev/null +++ b/salpa-admin/src/main/resources/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/salpa-admin/src/main/resources/.idea/modules.xml b/salpa-admin/src/main/resources/.idea/modules.xml new file mode 100644 index 0000000..e878207 --- /dev/null +++ b/salpa-admin/src/main/resources/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/salpa-admin/src/main/resources/.idea/resources.iml b/salpa-admin/src/main/resources/.idea/resources.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/salpa-admin/src/main/resources/.idea/resources.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/salpa-admin/src/main/resources/.idea/vcs.xml b/salpa-admin/src/main/resources/.idea/vcs.xml new file mode 100644 index 0000000..4fce1d8 --- /dev/null +++ b/salpa-admin/src/main/resources/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/salpa-admin/src/main/resources/.idea/workspace.xml b/salpa-admin/src/main/resources/.idea/workspace.xml new file mode 100644 index 0000000..55e1045 --- /dev/null +++ b/salpa-admin/src/main/resources/.idea/workspace.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 1659944916476 + + + + + + \ No newline at end of file diff --git a/salpa-admin/src/main/resources/META-INF/spring-devtools.properties b/salpa-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..2b23f85 --- /dev/null +++ b/salpa-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson.*.jar \ No newline at end of file diff --git a/salpa-admin/src/main/resources/application-druid.yml b/salpa-admin/src/main/resources/application-druid.yml new file mode 100644 index 0000000..d58f764 --- /dev/null +++ b/salpa-admin/src/main/resources/application-druid.yml @@ -0,0 +1,57 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://192.168.1.168:3307/metaops?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 + username: metaops + password: Metaops@123 + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 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/salpa-admin/src/main/resources/application.yml b/salpa-admin/src/main/resources/application.yml new file mode 100644 index 0000000..e3326a1 --- /dev/null +++ b/salpa-admin/src/main/resources/application.yml @@ -0,0 +1,123 @@ +# 项目相关配置 +metaops: + # 名称 + name: Salpa + # 版本 + version: 3.8.3 + # 版权年份 + copyrightYear: 2022 + # 实例演示开关 + demoEnabled: true + # 文件路径 示例( Windows配置D:/metaops/uploadPath,Linux配置 /home/metaops/uploadPath) + profile: /home/metaops/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数组计算 char 字符验证 + captchaType: math + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 9080 + 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.metaops: debug + org.springframework: warn + +# Spring配置 +spring: + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + profiles: + active: druid + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 1024MB + # 设置总上传的文件大小 + max-request-size: 1024MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + # redis 配置 + redis: + # 地址 + host: 192.168.1.168 + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 10 + # 密码 + 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.salpa.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml + +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /dev-api + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* diff --git a/salpa-admin/src/main/resources/banner.txt b/salpa-admin/src/main/resources/banner.txt new file mode 100644 index 0000000..0931cb8 --- /dev/null +++ b/salpa-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/salpa-admin/src/main/resources/empty.json b/salpa-admin/src/main/resources/empty.json new file mode 100644 index 0000000..fc3abbf --- /dev/null +++ b/salpa-admin/src/main/resources/empty.json @@ -0,0 +1,1292 @@ +{ + "name": "empty", + "describe": "empty", + "avatar": "", + "version": "4.1.0", + "createdTime": "2022-5-9 06:43:39", + "updatedTime": "2022-5-9 07:24:55", + "dbConns": [], + "profile": { + "default": { + "db": "29D1CE08-4C35-4D2D-AAA9-23D93305B52E", + "dbConn": "", + "entityInitFields": [ + { + "defKey": "TENANT_ID", + "defName": "租户号", + "comment": "", + "type": "", + "len": 32, + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "domain": "16120F75-6AA7-4483-868D-F07F511BB081", + "refDict": "", + "uiHint": "", + "id": "ADB3AD14-6603-43E2-8261-114E32442B5B" + }, + { + "defKey": "REVISION", + "defName": "乐观锁", + "comment": "", + "domain": "16120F75-6AA7-4483-868D-F07F511BB081", + "type": "", + "len": 32, + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "uiHint": "", + "id": "92BF430E-01FA-4AEF-944F-25A142632654" + }, + { + "defKey": "CREATED_BY", + "defName": "创建人", + "comment": "", + "domain": "16120F75-6AA7-4483-868D-F07F511BB081", + "type": "", + "len": 32, + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "uiHint": "", + "id": "C8BE2C7A-8251-4ADD-BB4F-411C5754DA62" + }, + { + "defKey": "CREATED_TIME", + "defName": "创建时间", + "comment": "", + "domain": "7CFFA0D3-6A93-4DDC-BC10-DF21211064DC", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "uiHint": "", + "id": "4E471FD6-3E73-4A90-B660-51598A482409" + }, + { + "defKey": "UPDATED_BY", + "defName": "更新人", + "comment": "", + "domain": "16120F75-6AA7-4483-868D-F07F511BB081", + "type": "", + "len": 32, + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "uiHint": "", + "id": "0DC24AA9-4CD0-45D8-95CF-FA546BE343AB" + }, + { + "defKey": "UPDATED_TIME", + "defName": "更新时间", + "comment": "", + "domain": "7CFFA0D3-6A93-4DDC-BC10-DF21211064DC", + "type": "", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "uiHint": "", + "id": "09F64AC4-4DEE-428F-AF64-4C103884E1AC" + } + ], + "entityInitProperties": { + "partitioned by": "(date string)", + "row format delimited": "", + "fields terminated by ','": "", + "collection items terminated by '-'": "", + "map keys terminated by ':'": "", + "store as textfile;": "" + } + }, + "javaHome": "", + "sql": { + "delimiter": "" + }, + "dataTypeSupports": [ + { + "defKey": "MYSQL", + "id": "29D1CE08-4C35-4D2D-AAA9-23D93305B52E" + }, + { + "defKey": "ORACLE", + "id": "A4E23CB7-BB01-4BD1-9F71-F73F3E15A542" + }, + { + "defKey": "SQLServer", + "id": "BFC87171-C74F-494A-B7C2-76B9C55FACC9" + }, + { + "defKey": "PostgreSQL", + "id": "DFBEC1DD-AA84-456E-BBF3-C95DD0DB2022" + }, + { + "defKey": "DB2", + "id": "89504F5D-94BF-4C9E-8B2E-44F37305FED5" + }, + { + "defKey": "DM", + "id": "0BBCABA5-B8E4-41B0-B8E4-8F5EA6029307" + }, + { + "defKey": "GaussDB", + "id": "592C7013-143D-4E7B-AF64-0D7BF1E28230" + }, + { + "defKey": "Kingbase", + "id": "77BD85E5-9D0D-4096-8427-CBA306FC9C6A" + }, + { + "defKey": "MaxCompute", + "id": "11D1FB71-A587-4217-89BA-611B8A1F83E0" + }, + { + "defKey": "SQLite", + "id": "B363BE0B-F852-49B8-9B2E-F6D2174DEAC1" + }, + { + "defKey": "JAVA", + "id": "797A1496-D649-4261-89B4-544132EC3F36" + }, + { + "defKey": "JavaMybatis", + "id": "895CFD1D-4273-4D32-A2C4-CAC70200AB5B" + }, + { + "defKey": "JavaMybatisPlus", + "id": "A2EE7B4A-CE62-4290-B00C-B26C1BF18073" + }, + { + "defKey": "C#", + "id": "F3AC2415-E86B-40C6-9FEB-F4B7937D2C30" + }, + { + "defKey": "Hive", + "id": "81CCA482-3F4D-4EAC-8CF9-F5E7BC098AD2" + }, + { + "defKey": "Golang", + "id": "B91D99E0-9B7C-416C-8737-B760957DAF09" + } + ], + "codeTemplates": [ + { + "type": "appCode", + "applyFor": "797A1496-D649-4261-89B4-544132EC3F36", + " JpaBean": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.entity;\n$blankline\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport javax.persistence.*;\nimport java.io.Serializable;\nimport java.util.Date;\n$blankline\n\n /**\n * {{=it.entity.defName}};{{=it.entity.comment}}\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@ApiModel(value = \"{{=it.entity.defName}}\",description = \"{{=it.entity.comment}}\")\n@Table(name=\"{{=it.entity.defKey}}\")\npublic class {{=beanClass}} implements Serializable,Cloneable{\n{{~it.entity.fields:field:index}}\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n {{? field.primaryKey }}\n @Id\n @GeneratedValue\n {{?}}\n @ApiModelProperty(name = \"{{=field.defName}}\",notes = \"{{=field.comment}}\")\n private {{=field.type}} {{=it.func.camel(field.defKey,false)}} ;\n{{~}}\n$blankline\n\n{{~it.entity.fields:field:index}}\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n public {{=field.type}} get{{=it.func.camel(field.defKey,true)}}(){\n return this.{{=it.func.camel(field.defKey,false)}};\n }\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n public void set{{=it.func.camel(field.defKey,true)}}({{=field.type}} {{= it.func.camel(field.defKey,false) }}){\n this.{{=it.func.camel(field.defKey,false)}}={{=it.func.camel(field.defKey,false)}};\n }\n{{~}}\n}" + }, + { + "type": "appCode", + "applyFor": "F3AC2415-E86B-40C6-9FEB-F4B7937D2C30", + "Default": "using System;\nusing System.Collections.Generic;\n\n$blankline\n{{\n var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n}}\n/*\n * @author : http://www.chiner.com.cn\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n * @desc : {{=it.func.join(it.entity.defName,it.entity.comment,'-')}}\n */\nnamespace PDManer.Application\n{\n public partial class {{=it.func.camel(it.entity.defKey,true) }}\n {\n \n {{~it.entity.fields:field:index}}\n /// \n /// {{=it.func.join(field.defName,field.comment,';')}}\n /// \n public {{=field.type}} {{=it.func.camel(field.defKey,true)}} { get; set; }\n $blankline\n {{~}}\n \n }\n}", + "SqlSugar": "using System;\nusing System.Collections.Generic;\nusing SqlSugar;\n\n$blankline\n{{\n var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n var sqlSugartable='[SugarTable(\"{{=it.entity.defKey}}\", TableDescription = \"{{=it.func.join(it.entity.defName,it.entity.comment,';')}}\")]';\n}}\n/*\n * @author : xkdong@163.com\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n * @desc : {{=it.func.join(it.entity.defName,it.entity.comment,'-')}}\n */\nnamespace Model.DBModel\n{\n /// \n /// {{=it.func.join(it.entity.defName,it.entity.comment,';')}}\n /// \n {{=sqlSugartable}}\n public class {{=it.entity.defKey}}\n {\n {{~it.entity.fields:field:index}}\n /// \n /// {{=it.func.join(field.defName,field.comment,';')}}\n /// \n {{? field.primaryKey }}\n [SugarColumn(IsIdentity = true, IsPrimaryKey = true)]\n {{?}}\n public {{=field.type}} {{=it.func.camel(field.defKey,true)}}{ get; set; }\n $blankline\n {{~}}\n }\n}" + }, + { + "applyFor": "895CFD1D-4273-4D32-A2C4-CAC70200AB5B", + "type": "appCode", + "Controller": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.controller;\n$blankline\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageRequest;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\nimport {{=pkgName}}.entity.{{=beanClass}};\nimport {{=pkgName}}.service.{{=serviceClass}};\n$blankline\n\n /**\n * {{=it.entity.defName}};({{=it.entity.defKey}})表控制层\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@Api(tags = \"{{=it.entity.defName}}对象功能接口\")\n@RestController\n@RequestMapping(\"/{{=it.func.camel(it.entity.defKey,false)}}\")\npublic class {{=beanClass}}Controller{\n @Autowired\n private {{=serviceClass}} {{=serviceVarName}};\n $blankline\n /** \n * 通过ID查询单条数据 \n *\n * @param {{=pkVarName}} 主键\n * @return 实例对象\n */\n @ApiOperation(\"通过ID查询单条数据\")\n @GetMapping(\"{{{=it.func.camel(pkVarName,false)}}}\")\n public ResponseEntity<{{=beanClass}}> queryById({{=pkDataType}} {{=pkVarName}}){\n return ResponseEntity.ok({{=serviceVarName}}.queryById({{=pkVarName}}));\n }\n $blankline\n /** \n * 分页查询\n *\n * @param {{=beanVarName}} 筛选条件\n * @param pageRequest 分页对象\n * @return 查询结果\n */\n @ApiOperation(\"分页查询\")\n @GetMapping\n public ResponseEntity> paginQuery({{=beanClass}} {{=beanVarName}}, PageRequest pageRequest){\n return ResponseEntity.ok({{=serviceVarName}}.paginQuery({{=beanVarName}}, pageRequest));\n }\n $blankline\n /** \n * 新增数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n @ApiOperation(\"新增数据\")\n @PostMapping\n public ResponseEntity<{{=beanClass}}> add({{=beanClass}} {{=beanVarName}}){\n return ResponseEntity.ok({{=serviceVarName}}.insert({{=beanVarName}}));\n }\n $blankline\n /** \n * 更新数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n @ApiOperation(\"更新数据\")\n @PutMapping\n public ResponseEntity<{{=beanClass}}> edit({{=beanClass}} {{=beanVarName}}){\n return ResponseEntity.ok({{=serviceVarName}}.update({{=beanVarName}}));\n }\n $blankline\n /** \n * 通过主键删除数据\n *\n * @param {{=pkVarName}} 主键\n * @return 是否成功\n */\n @ApiOperation(\"通过主键删除数据\")\n @DeleteMapping\n public ResponseEntity deleteById({{=pkDataType}} {{=pkVarName}}){\n return ResponseEntity.ok({{=serviceVarName}}.deleteById({{=pkVarName}}));\n }\n}", + "Service": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.service;\n$blankline\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageRequest;\nimport {{=pkgName}}.entity.{{=beanClass}};\n$blankline\n\n /**\n * {{=it.entity.defName}};({{=it.entity.defKey}})表服务接口\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\npublic interface {{=serviceClass}}{\n /** \n * 通过ID查询单条数据 \n *\n * @param {{=pkVarName}} 主键\n * @return 实例对象\n */\n {{=beanClass}} queryById({{=pkDataType}} {{=pkVarName}});\n \n /** \n * 分页查询\n *\n * @param {{=beanVarName}} 筛选条件\n * @param pageRequest 分页对象\n * @return 查询结果\n */\n Page<{{=beanClass}}> paginQuery({{=beanClass}} {{=beanVarName}}, PageRequest pageRequest);\n\n /** \n * 新增数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n {{=beanClass}} insert({{=beanClass}} {{=beanVarName}});\n\n \n /** \n * 更新数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n {{=beanClass}} update({{=beanClass}} {{=beanVarName}});\n\n /** \n * 通过主键删除数据\n *\n * @param {{=pkVarName}} 主键\n * @return 是否成功\n */\n boolean deleteById({{=pkDataType}} {{=pkVarName}});\n}", + "ServiceImpl": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkVarNameU = \"UndefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkVarNameU = it.func.camel(field.defKey,true);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n var mapperName = beanVarName+'Mapper';\n \n}}package {{=pkgName}}.service.impl;\n$blankline\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.PageImpl;\nimport org.springframework.data.domain.PageRequest;\nimport {{=pkgName}}.entity.{{=beanClass}};\nimport {{=pkgName}}.mapper.{{=beanClass}}Mapper;\nimport {{=pkgName}}.service.{{=serviceClass}};\n\n /**\n * {{=it.entity.defName}};({{=it.entity.defKey}})表服务实现类\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@Service\npublic class {{=serviceClass}}Impl implements {{=serviceClass}}{\n @Autowired\n private {{=beanClass}}Mapper {{=mapperName}};\n $blankline\n /** \n * 通过ID查询单条数据 \n *\n * @param {{=pkVarName}} 主键\n * @return 实例对象\n */\n public {{=beanClass}} queryById({{=pkDataType}} {{=pkVarName}}){\n return {{=mapperName}}.queryById({{=pkVarName}});\n }\n $blankline\n /** \n * 分页查询\n *\n * @param {{=beanVarName}} 筛选条件\n * @param pageRequest 分页对象\n * @return 查询结果\n */\n public Page<{{=beanClass}}> paginQuery({{=beanClass}} {{=beanVarName}}, PageRequest pageRequest){\n long total = {{=mapperName}}.count({{=beanVarName}});\n return new PageImpl<>({{=mapperName}}.queryAllByLimit({{=beanVarName}}, pageRequest), pageRequest, total);\n }\n $blankline\n /** \n * 新增数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n public {{=beanClass}} insert({{=beanClass}} {{=beanVarName}}){\n {{=mapperName}}.insert({{=beanVarName}});\n return {{=beanVarName}};\n }\n $blankline\n /** \n * 更新数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n public {{=beanClass}} update({{=beanClass}} {{=beanVarName}}){\n {{=mapperName}}.update({{=beanVarName}});\n return queryById({{=beanVarName}}.get{{=pkVarNameU}}());\n }\n $blankline\n /** \n * 通过主键删除数据\n *\n * @param {{=pkVarName}} 主键\n * @return 是否成功\n */\n public boolean deleteById({{=pkDataType}} {{=pkVarName}}){\n int total = {{=mapperName}}.deleteById({{=pkVarName}});\n return total > 0;\n }\n}", + "Mapper": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.mapper;\n$blankline\nimport java.util.List;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\nimport org.springframework.data.domain.Pageable;\nimport {{=pkgName}}.entity.{{=beanClass}};\n$blankline\n\n /**\n * {{=it.entity.defName}};({{=it.entity.defKey}})表数据库访问层\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@Mapper\npublic interface {{=beanClass}}Mapper{\n /** \n * 通过ID查询单条数据 \n *\n * @param {{=pkVarName}} 主键\n * @return 实例对象\n */\n {{=beanClass}} queryById({{=pkDataType}} {{=pkVarName}});\n \n /** \n * 分页查询指定行数据\n *\n * @param {{=beanVarName}} 查询条件\n * @param pageable 分页对象\n * @return 对象列表\n */\n List<{{=beanClass}}> queryAllByLimit({{=beanClass}} {{=beanVarName}}, @Param(\"pageable\") Pageable pageable);\n\n /** \n * 统计总行数\n *\n * @param {{=beanVarName}} 查询条件\n * @return 总行数\n */\n long count({{=beanClass}} {{=beanVarName}});\n\n /** \n * 新增数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 影响行数\n */\n int insert({{=beanClass}} {{=beanVarName}});\n\n /** \n * 批量新增数据\n *\n * @param entities List<{{=beanClass}}> 实例对象列表\n * @return 影响行数\n */\n int insertBatch(@Param(\"entities\") List<{{=beanClass}}> entities);\n \n /** \n * 批量新增或按主键更新数据\n *\n * @param entities List<{{=beanClass}}> 实例对象列表\n * @return 影响行数\n */\n int insertOrUpdateBatch(@Param(\"entities\") List<{{=beanClass}}> entities);\n \n /** \n * 更新数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 影响行数\n */\n int update({{=beanClass}} {{=beanVarName}});\n\n /** \n * 通过主键删除数据\n *\n * @param {{=pkVarName}} 主键\n * @return 影响行数\n */\n int deleteById({{=pkDataType}} {{=pkVarName}});\n}", + "Mapper.xml": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n var pkField = \"UNDEFINED_ID\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkField = field.defKey;\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}\n\n\n \n {{~it.entity.fields:field:index}}\n \n {{~}}\n \n $blankline\n \n \n $blankline\n \n \n $blankline\n \n \n $blankline\n \n \n insert into {{=it.entity.defKey}}({{=it.entity.fields.map(function(e,i){return e.defKey}).join(',')}})\n values ({{=it.entity.fields.map(function(e,i){return '#{'+it.func.camel(e.defKey,false)+'}'}).join(',')}})\n \n $blankline\n \n \n insert into {{=it.entity.defKey}}({{=it.entity.fields.map(function(e,i){return e.defKey}).join(',')}})\n values\n \n ({{=it.entity.fields.map(function(e,i){return '#{entity.'+it.func.camel(e.defKey,false)+'}'}).join(',')}})\n \n \n $blankline\n \n \n insert into {{=it.entity.defKey}}({{=it.entity.fields.map(function(e,i){return e.defKey}).join(',')}})\n values\n \n ({{=it.entity.fields.map(function(e,i){return '#{entity.'+it.func.camel(e.defKey,false)+'}'}).join(',')}})\n \n on duplicate key update\n {{=it.entity.fields.map(function(e,i){return e.defKey + '=values('+e.defKey+')'}).join(',\\n\\t\\t')}}\n \n $blankline\n \n \n update {{=it.entity.defKey}}\n \n {{~it.entity.fields:field:index}}\n \n {{=field.defKey}} = #{{{=it.func.camel(field.defKey,false)}}},\n \n {{~}}\n \n where {{=pkField}} = #{{{=pkVarName}}}\n \n $blankline\n \n \n delete from {{=it.entity.defKey}} where {{=pkField}} = #{{{=pkVarName}}}\n \n\n\n", + "Entity": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.entity;\n$blankline\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport java.io.Serializable;\nimport java.util.Date;\n$blankline\n\n /**\n * {{=it.entity.defName}};{{=it.entity.comment}}\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@ApiModel(value = \"{{=it.entity.defName}}\",description = \"{{=it.entity.comment}}\")\npublic class {{=beanClass}} implements Serializable,Cloneable{\n{{~it.entity.fields:field:index}}\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n @ApiModelProperty(name = \"{{=field.defName}}\",notes = \"{{=field.comment}}\")\n private {{=field.type}} {{=it.func.camel(field.defKey,false)}} ;\n{{~}}\n$blankline\n\n{{~it.entity.fields:field:index}}\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n public {{=field.type}} get{{=it.func.camel(field.defKey,true)}}(){\n return this.{{=it.func.camel(field.defKey,false)}};\n }\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n public void set{{=it.func.camel(field.defKey,true)}}({{=field.type}} {{= it.func.camel(field.defKey,false) }}){\n this.{{=it.func.camel(field.defKey,false)}}={{=it.func.camel(field.defKey,false)}};\n }\n{{~}}\n}" + }, + { + "applyFor": "A2EE7B4A-CE62-4290-B00C-B26C1BF18073", + "type": "appCode", + "Controller": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.controller;\n$blankline\nimport java.util.List;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.data.domain.PageImpl;\nimport org.springframework.data.domain.PageRequest;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\nimport {{=pkgName}}.entity.{{=beanClass}};\nimport {{=pkgName}}.service.{{=serviceClass}};\n$blankline\n\n /**\n * {{=it.entity.defName}};({{=it.entity.defKey}})表控制层\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@Api(tags = \"{{=it.entity.defName}}对象功能接口\")\n@RestController\n@RequestMapping(\"/{{=it.func.camel(it.entity.defKey,false)}}\")\npublic class {{=beanClass}}Controller{\n @Autowired\n private {{=serviceClass}} {{=serviceVarName}};\n $blankline\n /** \n * 通过ID查询单条数据 \n *\n * @param {{=pkVarName}} 主键\n * @return 实例对象\n */\n @ApiOperation(\"通过ID查询单条数据\")\n @GetMapping(\"{{{=it.func.camel(pkVarName,false)}}}\")\n public ResponseEntity<{{=beanClass}}> queryById({{=pkDataType}} {{=pkVarName}}){\n return ResponseEntity.ok({{=serviceVarName}}.queryById({{=pkVarName}}));\n }\n $blankline\n /** \n * 分页查询\n *\n * @param {{=beanVarName}} 筛选条件\n * @param pageRequest 分页对象\n * @return 查询结果\n */\n @ApiOperation(\"分页查询\")\n @GetMapping\n public ResponseEntity> paginQuery({{=beanClass}} {{=beanVarName}}, PageRequest pageRequest){\n //1.分页参数\n long current = pageRequest.getPageNumber();\n long size = pageRequest.getPageSize();\n\n //2.分页查询\n /*把Mybatis的分页对象做封装转换,MP的分页对象上有一些SQL敏感信息,还是通过spring的分页模型来封装数据吧*/\n com.baomidou.mybatisplus.extension.plugins.pagination.Page<{{=beanClass}}> pageResult = {{=serviceVarName}}.paginQuery({{=beanVarName}}, current,size);\n\n //3. 分页结果组装\n List<{{=beanClass}}> dataList = pageResult.getRecords();\n long total = pageResult.getTotal();\n PageImpl<{{=beanClass}}> retPage = new PageImpl<{{=beanClass}}>(dataList,pageRequest,total);\n return ResponseEntity.ok(retPage);\n }\n $blankline\n /** \n * 新增数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n @ApiOperation(\"新增数据\")\n @PostMapping\n public ResponseEntity<{{=beanClass}}> add({{=beanClass}} {{=beanVarName}}){\n return ResponseEntity.ok({{=serviceVarName}}.insert({{=beanVarName}}));\n }\n $blankline\n /** \n * 更新数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n @ApiOperation(\"更新数据\")\n @PutMapping\n public ResponseEntity<{{=beanClass}}> edit({{=beanClass}} {{=beanVarName}}){\n return ResponseEntity.ok({{=serviceVarName}}.update({{=beanVarName}}));\n }\n $blankline\n /** \n * 通过主键删除数据\n *\n * @param {{=pkVarName}} 主键\n * @return 是否成功\n */\n @ApiOperation(\"通过主键删除数据\")\n @DeleteMapping\n public ResponseEntity deleteById({{=pkDataType}} {{=pkVarName}}){\n return ResponseEntity.ok({{=serviceVarName}}.deleteById({{=pkVarName}}));\n }\n}", + "Service": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.service;\n$blankline\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport {{=pkgName}}.entity.{{=beanClass}};\n$blankline\n\n /**\n * {{=it.entity.defName}};({{=it.entity.defKey}})表服务接口\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\npublic interface {{=serviceClass}}{\n $blankline\n /** \n * 通过ID查询单条数据 \n *\n * @param {{=pkVarName}} 主键\n * @return 实例对象\n */\n {{=beanClass}} queryById({{=pkDataType}} {{=pkVarName}});\n $blankline\n /**\n * 分页查询\n *\n * @param {{=beanVarName}} 筛选条件\n * @param current 当前页码\n * @param size 每页大小\n * @return\n */\n Page<{{=beanClass}}> paginQuery({{=beanClass}} {{=beanVarName}}, long current, long size);\n\n /** \n * 新增数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n {{=beanClass}} insert({{=beanClass}} {{=beanVarName}});\n\n \n /** \n * 更新数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n {{=beanClass}} update({{=beanClass}} {{=beanVarName}});\n\n /** \n * 通过主键删除数据\n *\n * @param {{=pkVarName}} 主键\n * @return 是否成功\n */\n boolean deleteById({{=pkDataType}} {{=pkVarName}});\n}", + "ServiceImpl": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkVarNameU = \"UndefinedId\";\n var pkFieldKey = \"UNDEFINED\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkFieldKey = field.defKey;\n pkVarName = it.func.camel(field.defKey,false);\n pkVarNameU = it.func.camel(field.defKey,true);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n var mapperName = beanVarName+'Mapper';\n \n}}package {{=pkgName}}.service.impl;\n$blankline\nimport cn.hutool.core.util.StrUtil;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;\n\nimport {{=pkgName}}.entity.{{=beanClass}};\nimport {{=pkgName}}.mapper.{{=beanClass}}Mapper;\nimport {{=pkgName}}.service.{{=serviceClass}};\n\n /**\n * {{=it.entity.defName}};({{=it.entity.defKey}})表服务实现类\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@Service\npublic class {{=serviceClass}}Impl implements {{=serviceClass}}{\n @Autowired\n private {{=beanClass}}Mapper {{=mapperName}};\n $blankline\n /** \n * 通过ID查询单条数据 \n *\n * @param {{=pkVarName}} 主键\n * @return 实例对象\n */\n public {{=beanClass}} queryById({{=pkDataType}} {{=pkVarName}}){\n return {{=mapperName}}.selectById({{=pkVarName}});\n }\n $blankline\n /**\n * 分页查询\n *\n * @param {{=beanVarName}} 筛选条件\n * @param current 当前页码\n * @param size 每页大小\n * @return\n */\n public Page<{{=beanClass}}> paginQuery({{=beanClass}} {{=beanVarName}}, long current, long size){\n //1. 构建动态查询条件\n LambdaQueryWrapper<{{=beanClass}}> queryWrapper = new LambdaQueryWrapper<>();\n {{~it.entity.fields.filter(function(e){return e[\"type\"]===\"String\"&&e.defKey !== pkFieldKey}):field:index}}\n if(StrUtil.isNotBlank({{=beanVarName}}.get{{=it.func.camel(field.defKey,true)}}())){\n queryWrapper.eq({{=beanClass}}::get{{=it.func.camel(field.defKey,true)}}, {{=beanVarName}}.get{{=it.func.camel(field.defKey,true)}}());\n }\n {{~}}\n\n //2. 执行分页查询\n Page<{{=beanClass}}> pagin = new Page<>(current , size , true);\n IPage<{{=beanClass}}> selectResult = {{=mapperName}}.selectByPage(pagin , queryWrapper);\n pagin.setPages(selectResult.getPages());\n pagin.setTotal(selectResult.getTotal());\n pagin.setRecords(selectResult.getRecords());\n\n //3. 返回结果\n return pagin;\n }\n $blankline\n /** \n * 新增数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n public {{=beanClass}} insert({{=beanClass}} {{=beanVarName}}){\n {{=mapperName}}.insert({{=beanVarName}});\n return {{=beanVarName}};\n }\n $blankline\n /** \n * 更新数据\n *\n * @param {{=beanVarName}} 实例对象\n * @return 实例对象\n */\n public {{=beanClass}} update({{=beanClass}} {{=beanVarName}}){\n //1. 根据条件动态更新\n LambdaUpdateChainWrapper<{{=beanClass}}> chainWrapper = new LambdaUpdateChainWrapper<{{=beanClass}}>({{=mapperName}});\n {{~it.entity.fields.filter(function(e){return e[\"type\"]===\"String\"&&e.defKey !== pkFieldKey}):field:index}}\n if(StrUtil.isNotBlank({{=beanVarName}}.get{{=it.func.camel(field.defKey,true)}}())){\n chainWrapper.eq({{=beanClass}}::get{{=it.func.camel(field.defKey,true)}}, {{=beanVarName}}.get{{=it.func.camel(field.defKey,true)}}());\n }\n {{~}}\n //2. 设置主键,并更新\n chainWrapper.set({{=beanClass}}::get{{=pkVarNameU}}, {{=beanVarName}}.get{{=pkVarNameU}}());\n boolean ret = chainWrapper.update();\n //3. 更新成功了,查询最最对象返回\n if(ret){\n return queryById({{=beanVarName}}.get{{=pkVarNameU}}());\n }else{\n return {{=beanVarName}};\n }\n }\n $blankline\n /** \n * 通过主键删除数据\n *\n * @param {{=pkVarName}} 主键\n * @return 是否成功\n */\n public boolean deleteById({{=pkDataType}} {{=pkVarName}}){\n int total = {{=mapperName}}.deleteById({{=pkVarName}});\n return total > 0;\n }\n}", + "Mapper": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.mapper;\n$blankline\n\nimport com.baomidou.mybatisplus.core.conditions.Wrapper;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.Constants;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\nimport {{=pkgName}}.entity.{{=beanClass}};\n$blankline\n\n /**\n * {{=it.entity.defName}};({{=it.entity.defKey}})表数据库访问层\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@Mapper\npublic interface {{=beanClass}}Mapper extends BaseMapper<{{=beanClass}}>{\n /** \n * 分页查询指定行数据\n *\n * @param page 分页参数\n * @param wrapper 动态查询条件\n * @return 分页对象列表\n */\n IPage<{{=beanClass}}> selectByPage(IPage<{{=beanClass}}> page , @Param(Constants.WRAPPER) Wrapper<{{=beanClass}}> wrapper);\n}", + "Mapper.xml": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n var pkField = \"UNDEFINED_ID\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkField = field.defKey;\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}\n\n$blankline\n\n\n \n\n\n", + "Entity": "{{ var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1;\n var days=today.getDate();\n \n var pkVarName = \"undefinedId\";\n var pkDataType = \"String\";\n it.entity.fields.forEach(function(field){\n if(field.primaryKey){\n pkVarName = it.func.camel(field.defKey,false);\n pkDataType = field[\"type\"];\n return;\n }\n });\n \n var pkgName = it.entity.env.base.nameSpace;\n var beanClass = it.entity.env.base.codeRoot;\n var beanVarName = beanClass.charAt(0).toLowerCase()+beanClass.slice(1);\n var serviceClass = beanClass+'Service';\n var serviceVarName= beanVarName+'Service';\n \n}}package {{=pkgName}}.entity;\n$blankline\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport java.io.Serializable;\nimport java.util.Date;\n$blankline\n\n /**\n * {{=it.entity.defName}};{{=it.entity.comment}}\n * @author : http://www.chiner.pro\n * @date : {{=fullYear}}-{{=month}}-{{=days}}\n */\n@ApiModel(value = \"{{=it.entity.defName}}\",description = \"{{=it.entity.comment}}\")\n@TableName(\"{{=it.entity.defKey}}\")\npublic class {{=beanClass}} implements Serializable,Cloneable{\n{{~it.entity.fields:field:index}}\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n @ApiModelProperty(name = \"{{=field.defName}}\",notes = \"{{=field.comment}}\")\n {{? field.primaryKey }}\n @TableId\n {{?}}\n private {{=field.type}} {{=it.func.camel(field.defKey,false)}} ;\n{{~}}\n$blankline\n\n{{~it.entity.fields:field:index}}\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n public {{=field.type}} get{{=it.func.camel(field.defKey,true)}}(){\n return this.{{=it.func.camel(field.defKey,false)}};\n }\n /** {{=it.func.join(field.defName,field.comment,';')}} */\n public void set{{=it.func.camel(field.defKey,true)}}({{=field.type}} {{= it.func.camel(field.defKey,false) }}){\n this.{{=it.func.camel(field.defKey,false)}}={{=it.func.camel(field.defKey,false)}};\n }\n{{~}}\n}" + }, + { + "applyFor": "29D1CE08-4C35-4D2D-AAA9-23D93305B52E", + "type": "dbDDL", + "createTable": "DROP TABLE IF EXISTS {{=it.entity.defKey}};\nCREATE TABLE {{=it.entity.defKey}}(\n{{ pkList = [] ; }}\n{{~it.entity.fields:field:index}}\n {{? field.primaryKey }}{{ pkList.push(field.defKey) }}{{?}}\n {{=field.defKey}} {{=field.dbType}}{{?field.len>0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{?}} {{= field.notNull ? 'NOT NULL' : '' }} {{= field.autoIncrement ? 'AUTO_INCREMENT' : '' }} {{= field.defaultValue ? it.func.join('DEFAULT',field.defaultValue,' ') : '' }} COMMENT '{{=it.func.join(field.defName,field.comment,';')}}' {{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i'+(after.defKey||'NULL'));\n }\n if(before.defName !== after.defName){\n ret.push('显示名称:'+(before.defName||'NULL')+'->'+(after.defName||'NULL'));\n }\n if(before.comment !== after.comment){\n ret.push('说明:'+(before.comment||'NULL')+'->'+(after.comment||'NULL'));\n }\n if(ret.length>0){\n return ' 基本信息:\\n\\t'+ret.join('\\n\\t');\n }\n return '';\n };\n \n function buildAddedDesc(entity){\n let ret = [];\n let fieldAdded = entity.data.fieldAdded||[];\n \n for (let field of fieldAdded) { \n let row = [];\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n \n function buildRemovedDesc(entity){\n let ret = [];\n let fieldRemoved = entity.data.fieldRemoved||[];\n \n for (let field of fieldRemoved) { \n let row = [];\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n \n function buildModifiedDesc(entity){\n let ret = [];\n let fieldModified = entity.data.fieldModified||[];\n \n for (let field1 of fieldModified) { \n let row = [];\n let field = field1.before;\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n}}\n\n\n{{? createEntities && createEntities.length > 0}}\n/* --------------- 创建表 --------------- */\n{{~ createEntities:entity}}\n{{=entity.data.defKey}}[{{=entity.data.defName}}]\n{{~}}\n{{?}}\n\n\n{{? dropEntities && dropEntities.length > 0}}\n/* --------------- 删除表 --------------- */\n{{~ dropEntities:entity}}\n{{=entity.data.defKey}}[{{=entity.data.defName}}]\n{{~}}\n{{?}}\n\n{{? modifyEntities && modifyEntities.length > 0}}\n/* --------------- 修改表 --------------- */\n{{~ modifyEntities:entity}}\n{{=entity.data.baseInfo.defKey}}[{{=entity.data.baseInfo.defName}}]{{let changeText=baseChanged(entity.data.baseChanged);}}\n{{=baseChanged(entity.data.baseChanged)}}\n {{? entity.data.fieldModified && entity.data.fieldModified.length > 0}}\n 修改字段:\n {{='\\t'}}{{=buildModifiedDesc(entity).join('\\n\\t')}}{{?}}{{\n /*计算是否调整了属性*/\n let propAdded = entity.data.propAdded || [];\n let propRemoved = entity.data.propRemoved || [];\n let propModified = entity.data.propModified || [];\n let changed = propAdded.length>0 || propRemoved.length>0 || propModified.length>0;\n /*计算关联是否调整*/\n let refEntityAdd = entity.data.refEntityAdd || [];\n let refEntityRemoved = entity.data.refEntityRemoved || [];\n let relaArray = [];\n for (let rela of refEntityAdd) {\n relaArray.push('建立关联:'+rela.defKey+'['+rela.defName+']');\n }\n for (let rela of refEntityRemoved) {\n relaArray.push('解除关联:'+rela.defKey+'['+rela.defName+']');\n }\n /*索引是否修改过*/\n let indexChanged = entity.data.indexChanged;\n }}{{=indexChanged?'\\n\\t更改了索引':''}}{{=changed?'\\n\\t更改了属性':''}}{{=relaArray.length>0?('\\n\\t'+relaArray.join('\\n\\t')):''}}\n{{? entity.data.fieldAdded && entity.data.fieldAdded.length > 0}}\n 添加字段:\n{{='\\t'}}{{=buildAddedDesc(entity).join('\\n\\t')}}\n{{?}}{{? entity.data.fieldRemoved && entity.data.fieldRemoved.length > 0}}\n 删除字段:\n{{='\\t'}}{{=buildRemovedDesc(entity).join('\\n\\t')}}\n{{?}}\n{{~}}\n{{?}}\n", + "update": "{{\n let createEntities = it.changes.filter(function(row){return (row.opt==='add'&&row['type']==='entity');});\n let dropEntities = it.changes.filter(function(row){return (row.opt==='delete'&&row['type']==='entity');});\n let modifyEntities = it.changes.filter(function(row){return (row.opt==='update'&&row['type']==='entity');});\n \n function baseChangedDDL(beforeAfter){\n if(beforeAfter == null){\n return '';\n }\n let ret = [];\n let before = beforeAfter.before || {\"defKey\":\"\",\"defName\":\"\",\"comment\":\"\"};\n let after = beforeAfter.after || {\"defKey\":\"\",\"defName\":\"\",\"comment\":\"\"};\n if(before.defKey !== after.defKey){\n ret.push('ALTER TABLE '+before.defKey+' RENAME TO '+after.defKey);\n }\n let commentText = '';\n let commentChanged = false;\n if(before.defName !== after.defName){\n commentText = after.defName;\n commentChanged = true;\n }\n if(before.comment !== after.comment){\n commentChanged = true;\n if(commentText){\n commentText = (commentText+ ';'+after.comment)\n }else{\n commentText = after.comment\n }\n }\n if(commentChanged){\n ret.push('ALTER TABLE '+after.defKey+' COMMENT \\''+commentText+'\\'');\n }\n let baseText = '-- 基本信息:\\n';\n return baseText+ret.join(';\\n')+';';\n };\n \n function buildAddedDDL(entity){\n let ret = [];\n let fieldAdded = entity.data.fieldAdded||[];\n if(fieldAdded.length == 0){\n return '';\n }\n \n let firstDDL = 'ALTER TABLE '+entity.data.baseInfo.defKey;\n for (let field of fieldAdded) { \n let ddlItem = 'ADD COLUMN '+field.defKey+' '+field.dbType;\n /*处理数据类型长度*/\n if(field.len>0){\n ddlItem += ('('+field.len);\n if(parseInt(field.scale)>0){\n ddlItem += (','+field.scale);\n }\n ddlItem += ')';\n }\n if(field.notNull){\n ddlItem += ' NOT NULL';\n }\n if(field.autoIncrement){\n ddlItem += ' AUTO_INCREMENT';\n }\n if(field.defaultValue){\n ddlItem += (' DEFAULT' + field.defaultValue);\n }\n ddlItem += (' COMMENT \\''+field.defName+';'+field.comment+'\\'');\n \n if(field.index>0 && field.afterFieldKey){\n ddlItem += (' AFTER '+field.afterFieldKey);\n }\n ret.push(ddlItem);\n }\n return firstDDL+'\\n'+ret.join(',\\n');\n };\n \n function buildRemovedDDL(entity){\n let ret = [];\n let fieldRemoved = entity.data.fieldRemoved||[];\n if(fieldRemoved.length == 0){\n return '';\n }\n \n let firstDDL = 'ALTER TABLE '+entity.data.baseInfo.defKey;\n for (let field of fieldRemoved) { \n ret.push('DROP '+field.defKey);\n }\n return firstDDL+'\\n'+ret.join(',\\n');\n };\n \n function buildModifiedDDL(entity){\n let ret = [];\n let fieldModified = entity.data.fieldModified||[];\n \n let firstDDL = 'ALTER TABLE '+entity.data.baseInfo.defKey;\n for (let field of fieldModified) { \n let changeDDL = '';\n let before = field.before || {};\n let after = field.after || {};\n if(before.defKey === after.defKey){\n changeDDL += (' MODIFY COLUMN '+after.defKey);\n }else{\n changeDDL += (' CHANGE COLUMN '+before.defKey+' '+after.defKey);\n }\n changeDDL += (' '+after.dbType);\n if(after.len>0){\n changeDDL += ('('+after.len);\n if(parseInt(field.scale)>0){\n changeDDL += (','+field.scale);\n }\n changeDDL += ')';\n }\n if(after.notNull){\n changeDDL += ' NOT NULL';\n }\n let defaultValue = '';\n if(after.defaultValue != null && after.defaultValue.length>0){\n defaultValue = (after.defaultValue);\n }else{\n defaultValue = 'NULL';\n }\n changeDDL += (' DEFAULT ' + defaultValue);\n let comment = after.comment||'';\n changeDDL += (' COMMENT \\''+comment+'\\';');\n ret.push(firstDDL+' '+changeDDL);\n }\n return ret;\n };\n}}\n{{? createEntities && createEntities.length > 0}}\n/* --------------- 创建表 --------------- */\n{{~ createEntities:entity}}\n{{=it.func.createDDL(entity.data,entity['type'])}}\n{{~}}\n{{?}}\n\n\n{{? dropEntities && dropEntities.length > 0}}\n/* --------------- 删除表 --------------- */\n{{~ dropEntities:entity}}\n{{=it.func.dropDDL(entity.data,entity['type'])}}\n{{~}}\n{{?}}\n\n\n{{? modifyEntities && modifyEntities.length > 0}}\n{{~ modifyEntities:entity}}\n/* --------------- 修改表 --------------- */\n-- 修改表:{{=entity.data.baseInfo.defKey}}[{{=entity.data.baseInfo.defName}}]\n{{=baseChangedDDL(entity.data.baseChanged)}}\n{{? entity.data.fieldModified && entity.data.fieldModified.length > 0}}\n-- 修改字段:\n{{=buildModifiedDDL(entity).join('\\n')}}\n{{?}}{{\n/*索引是否修改过*/\nlet indexChanged = entity.data.indexChanged;\n}}\n{{? indexChanged }}\n{{=it.func.indexRebuildDDL(entity.data.baseInfo,entity.data.newIndexes,entity.data.fullFields,entity['type'])}}\n{{?}}\n\n{{? entity.data.fieldAdded && entity.data.fieldAdded.length > 0}}\n-- 添加字段:\n{{=buildAddedDDL(entity)}};\n{{?}}\n\n{{? entity.data.fieldRemoved && entity.data.fieldRemoved.length > 0}}\n-- 删除字段:\n{{=buildRemovedDDL(entity)}};\n{{?}}\n{{~}}\n{{?}}" + }, + { + "applyFor": "A4E23CB7-BB01-4BD1-9F71-F73F3E15A542", + "type": "dbDDL", + "createTable": "CREATE TABLE {{=it.entity.defKey}}(\n{{ pkList = [] ; }}\n{{~it.entity.fields:field:index}}\n {{? field.primaryKey }}{{ pkList.push(field.defKey) }}{{?}}\n {{=field.defKey}} {{=field.type}}{{?field.len>0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{?}}{{= field.defaultValue ? it.func.join(' DEFAULT ',field.defaultValue,' ') : '' }}{{= field.notNull ? ' NOT NULL' : '' }}{{= field.autoIncrement ? '' : '' }}{{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i'+(after.defKey||'NULL'));\n }\n if(before.defName !== after.defName){\n ret.push('显示名称:'+(before.defName||'NULL')+'->'+(after.defName||'NULL'));\n }\n if(before.comment !== after.comment){\n ret.push('说明:'+(before.comment||'NULL')+'->'+(after.comment||'NULL'));\n }\n if(ret.length>0){\n return ' 基本信息:\\n\\t'+ret.join('\\n\\t');\n }\n return '';\n };\n \n function buildAddedDesc(entity){\n let ret = [];\n let fieldAdded = entity.data.fieldAdded||[];\n \n for (let field of fieldAdded) { \n let row = [];\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n \n function buildRemovedDesc(entity){\n let ret = [];\n let fieldRemoved = entity.data.fieldRemoved||[];\n \n for (let field of fieldRemoved) { \n let row = [];\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n \n function buildModifiedDesc(entity){\n let ret = [];\n let fieldModified = entity.data.fieldModified||[];\n \n for (let field1 of fieldModified) { \n let row = [];\n let field = field1.before;\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n}}\n{{? createEntities && createEntities.length > 0}}\n/* -------------------------------------------------- */\n创建表:\n{{~ createEntities:entity}}\n {{=entity.data.defKey}}[{{=entity.data.defName}}]\n{{~}}\n{{?}}\n\n\n{{? dropEntities && dropEntities.length > 0}}\n/* -------------------------------------------------- */\n删除表:\n{{~ dropEntities:entity}}\n {{=entity.data.defKey}}[{{=entity.data.defName}}]\n{{~}}\n{{?}}\n\n\n{{? modifyEntities && modifyEntities.length > 0}}\n{{~ modifyEntities:entity}}\n/* -------------------------------------------------- */\n修改表:{{=entity.data.baseInfo.defKey}}[{{=entity.data.baseInfo.defName}}]{{let changeText=baseChanged(entity.data.baseChanged);}}\n{{=baseChanged(entity.data.baseChanged)}}\n {{? entity.data.fieldAdded && entity.data.fieldAdded.length > 0}}\n 添加字段:\n {{='\\t'}}{{=buildAddedDesc(entity).join('\\n\\t')}}\n {{?}}{{? entity.data.fieldRemoved && entity.data.fieldRemoved.length > 0}}\n 删除字段:\n {{='\\t'}}{{=buildRemovedDesc(entity).join('\\n\\t')}}\n {{?}}{{? entity.data.fieldModified && entity.data.fieldModified.length > 0}}\n 修改字段:\n {{='\\t'}}{{=buildModifiedDesc(entity).join('\\n\\t')}}\n {{?}}{{\n /*计算是否调整了属性*/\n let propAdded = entity.data.propAdded || [];\n let propRemoved = entity.data.propRemoved || [];\n let propModified = entity.data.propModified || [];\n let changed = propAdded.length>0 || propRemoved.length>0 || propModified.length>0;\n /*计算关联是否调整*/\n let refEntityAdd = entity.data.refEntityAdd || [];\n let refEntityRemoved = entity.data.refEntityRemoved || [];\n let relaArray = [];\n for (let rela of refEntityAdd) {\n relaArray.push('\\n\\t建立关联:'+rela.defKey+'['+rela.defName+']');\n }\n for (let rela of refEntityRemoved) {\n relaArray.push('\\n\\t解除关联:'+rela.defKey+'['+rela.defName+']');\n }\n /*索引是否修改过*/\n let indexChanged = entity.data.indexChanged;\n }}\n{{=indexChanged?'\\n\\t更改了索引':''}}\n{{=changed?'\\n\\t更改了属性':''}}\n{{=relaArray.length>0?relaArray.join(''):''}}\n{{~}}\n{{?}}", + "update": "{{\n let createEntities = it.changes.filter(function(row){return (row.opt==='add'&&row['type']==='entity');});\n let dropEntities = it.changes.filter(function(row){return (row.opt==='delete'&&row['type']==='entity');});\n let modifyEntities = it.changes.filter(function(row){return (row.opt==='update'&&row['type']==='entity');});\n \n function baseChangedDDL(beforeAfter){\n if(beforeAfter == null){\n return '';\n }\n let ret = [];\n let before = beforeAfter.before || {\"defKey\":\"\",\"defName\":\"\",\"comment\":\"\"};\n let after = beforeAfter.after || {\"defKey\":\"\",\"defName\":\"\",\"comment\":\"\"};\n if(before.defKey !== after.defKey){\n ret.push(`ALTER TABLE ${before.defKey} RENAME TO ${after.defKey}`);\n }\n let commentText = '';\n let commentChanged = false;\n if(before.defName !== after.defName){\n commentText = after.defName;\n commentChanged = true;\n }\n if(before.comment !== after.comment){\n commentChanged = true;\n if(commentText){\n commentText = (commentText+ ';'+after.comment)\n }else{\n commentText = after.comment\n }\n }\n if(commentChanged){\n let myText = `COMMENT ON TABLE ${after.defKey} IS '${commentText}'`;\n ret.push(myText);\n }\n let baseText = '-- 基本信息:\\n';\n return baseText+ret.join(';\\n')+';';\n };\n \n function buildAddedDDL(entity){\n let ret = [];\n let fieldAdded = entity.data.fieldAdded||[];\n if(fieldAdded.length == 0){\n return '';\n }\n \n let firstDDL = `ALTER TABLE ${entity.data.baseInfo.defKey}`;\n for (let field of fieldAdded) { \n let ddlItem = `ADD (${field.defKey} ${field.dbType}`;\n /*处理数据类型长度*/\n if(field.len>0){\n ddlItem += ('('+field.len);\n if(parseInt(field.scale)>0){\n ddlItem += (','+field.scale);\n }\n ddlItem += ')';\n }\n let defaultValue = field.defaultValue;\n defaultValue = (defaultValue==null)?\"\":(\"\"+defaultValue);\n if(defaultValue.length>0){\n ddlItem += (' DEFAULT ' + defaultValue);\n }\n if(field.notNull){\n ddlItem += ' NOT NULL';\n }\n ddlItem += ')';\n ret.push(`${firstDDL} ${ddlItem}`);\n \n /*处理字段注释*/\n let fieldComments = [];\n if(field.defName != null &&field.defName.length>0){\n fieldComments.push(field.defName);\n }\n if(field.comment != null &&field.comment.length>0){\n fieldComments.push(field.comment);\n }\n let commentText = fieldComments.join(';');\n if(commentText != null && commentText.length > 0){\n let commentDDL = `COMMENT ON COLUMN ${entity.data.baseInfo.defKey}.${field.defKey} IS '${commentText}'`;\n ret.push(commentDDL);\n }\n }\n return '\\n'+ret.join(';\\n');\n };\n \n function buildRemovedDDL(entity){\n let ret = [];\n let fieldRemoved = entity.data.fieldRemoved||[];\n if(fieldRemoved.length == 0){\n return '';\n }\n \n let firstDDL = `ALTER TABLE ${entity.data.baseInfo.defKey}`;\n for (let field of fieldRemoved) { \n ret.push(`${firstDDL} DROP COLUMN ${field.defKey}`);\n }\n return '\\n'+ret.join(';\\n');\n };\n \n function buildModifiedDDL(entity){\n let ret = [];\n let fieldModified = entity.data.fieldModified||[];\n \n let firstDDL = `ALTER TABLE ${entity.data.baseInfo.defKey}`;\n for (let field of fieldModified) { \n let changeDDL = '';\n let before = field.before || {};\n let after = field.after || {};\n if(before.defKey !== after.defKey){\n let renameText = `ALTER TABLE ${entity.data.baseInfo.defKey} RENAME COLUMN ${before.defKey} TO ${after.defKey};`;\n ret.push(renameText);\n }\n /*如果没有变化,则不生成变更语句*/\n if(before.dbType === after.dbType \n && before['len'] === after['len'] \n && before.scale === after.scale\n && before.primaryKey === after.primaryKey\n && before.notNull === after.notNull\n && before.autoIncrement === after.autoIncrement\n && before.defaultValue === after.defaultValue){\n continue;\n }\n changeDDL += ('MODIFY ('+after.defKey+'');\n changeDDL += (' '+after.dbType);\n if(after.len>0){\n changeDDL += ('('+after.len);\n if(parseInt(field.scale)>0){\n changeDDL += (','+field.scale);\n }\n changeDDL += ')';\n }\n let defaultValue = after.defaultValue;\n defaultValue = (defaultValue==null)?\"\":(\"\"+defaultValue);\n if(defaultValue.length>0){\n changeDDL += (' DEFAULT ' + defaultValue);\n }\n \n if(after.notNull){\n changeDDL += ' NOT NULL';\n }\n changeDDL += ')';\n ret.push(`${firstDDL} ${changeDDL};`);\n }\n return ret;\n };\n}}\n{{? createEntities && createEntities.length > 0}}\n/* --------------- 创建表 --------------- */\n{{~ createEntities:entity}}\n{{=it.func.createDDL(entity.data,entity['type'])}}\n{{~}}\n{{?}}\n\n\n{{? dropEntities && dropEntities.length > 0}}\n/* --------------- 删除表 --------------- */\n{{~ dropEntities:entity}}\n{{=it.func.dropDDL(entity.data,entity['type'])}}\n{{~}}\n{{?}}\n\n\n{{? modifyEntities && modifyEntities.length > 0}}\n{{~ modifyEntities:entity}}\n/* --------------- 修改表 --------------- */\n-- 修改表:{{=entity.data.baseInfo.defKey}}[{{=entity.data.baseInfo.defName}}]\n{{=baseChangedDDL(entity.data.baseChanged)}}\n{{? entity.data.fieldModified && entity.data.fieldModified.length > 0}}\n-- 修改字段:\n{{=buildModifiedDDL(entity).join('\\n')}}\n{{?}}{{\n/*索引是否修改过*/\nlet indexChanged = entity.data.indexChanged;\n}}\n{{? indexChanged }}\n{{=it.func.indexRebuildDDL(entity.data.baseInfo,entity.data.newIndexes,entity.data.fullFields,entity['type'])}}\n{{?}}\n\n{{? entity.data.fieldAdded && entity.data.fieldAdded.length > 0}}\n-- 添加字段:\n{{=buildAddedDDL(entity)}};\n{{?}}\n\n{{? entity.data.fieldRemoved && entity.data.fieldRemoved.length > 0}}\n-- 删除字段:\n{{=buildRemovedDDL(entity)}};\n{{?}}\n{{~}}\n{{?}}" + }, + { + "applyFor": "BFC87171-C74F-494A-B7C2-76B9C55FACC9", + "type": "dbDDL", + "createTable": "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[{{=it.entity.defKey}}]') AND type in (N'U')) DROP TABLE [dbo].[{{=it.entity.defKey}}];\n\nCREATE TABLE [dbo].[{{=it.entity.defKey}}](\n{{ pkList = [] ; }}\n{{~it.entity.fields:field:index}}\n {{? field.primaryKey }}{{ pkList.push(field.defKey) }}{{?}}\n {{=field.defKey}} {{=field.type}}{{?field.len>0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{?}}{{= field.notNull ? ' NOT NULL' : '' }}{{= field.autoIncrement ? ' IDENTITY(1,1)' : '' }}{{= field.defaultValue ? it.func.join(' DEFAULT ',field.defaultValue,' ') : '' }}{{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i'+(after.defKey||'NULL'));\n }\n if(before.defName !== after.defName){\n ret.push('显示名称:'+(before.defName||'NULL')+'->'+(after.defName||'NULL'));\n }\n if(before.comment !== after.comment){\n ret.push('说明:'+(before.comment||'NULL')+'->'+(after.comment||'NULL'));\n }\n if(ret.length>0){\n return ' 基本信息:\\n\\t'+ret.join('\\n\\t');\n }\n return '';\n };\n \n function buildAddedDesc(entity){\n let ret = [];\n let fieldAdded = entity.data.fieldAdded||[];\n \n for (let field of fieldAdded) { \n let row = [];\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n \n function buildRemovedDesc(entity){\n let ret = [];\n let fieldRemoved = entity.data.fieldRemoved||[];\n \n for (let field of fieldRemoved) { \n let row = [];\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n \n function buildModifiedDesc(entity){\n let ret = [];\n let fieldModified = entity.data.fieldModified||[];\n \n for (let field1 of fieldModified) { \n let row = [];\n let field = field1.before;\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n}}\n\n\n{{? createEntities && createEntities.length > 0}}\n/* --------------- 创建表 --------------- */\n{{~ createEntities:entity}}\n{{=entity.data.defKey}}[{{=entity.data.defName}}]\n{{~}}\n{{?}}\n\n\n{{? dropEntities && dropEntities.length > 0}}\n/* --------------- 删除表 --------------- */\n{{~ dropEntities:entity}}\n{{=entity.data.defKey}}[{{=entity.data.defName}}]\n{{~}}\n{{?}}\n\n{{? modifyEntities && modifyEntities.length > 0}}\n/* --------------- 修改表 --------------- */\n{{~ modifyEntities:entity}}\n{{=entity.data.baseInfo.defKey}}[{{=entity.data.baseInfo.defName}}]{{let changeText=baseChanged(entity.data.baseChanged);}}\n{{=baseChanged(entity.data.baseChanged)}}\n {{? entity.data.fieldModified && entity.data.fieldModified.length > 0}}\n 修改字段:\n {{='\\t'}}{{=buildModifiedDesc(entity).join('\\n\\t')}}{{?}}{{\n /*计算是否调整了属性*/\n let propAdded = entity.data.propAdded || [];\n let propRemoved = entity.data.propRemoved || [];\n let propModified = entity.data.propModified || [];\n let changed = propAdded.length>0 || propRemoved.length>0 || propModified.length>0;\n /*计算关联是否调整*/\n let refEntityAdd = entity.data.refEntityAdd || [];\n let refEntityRemoved = entity.data.refEntityRemoved || [];\n let relaArray = [];\n for (let rela of refEntityAdd) {\n relaArray.push('建立关联:'+rela.defKey+'['+rela.defName+']');\n }\n for (let rela of refEntityRemoved) {\n relaArray.push('解除关联:'+rela.defKey+'['+rela.defName+']');\n }\n /*索引是否修改过*/\n let indexChanged = entity.data.indexChanged;\n }}{{=indexChanged?'\\n\\t更改了索引':''}}{{=changed?'\\n\\t更改了属性':''}}{{=relaArray.length>0?('\\n\\t'+relaArray.join('\\n\\t')):''}}\n{{? entity.data.fieldAdded && entity.data.fieldAdded.length > 0}}\n 添加字段:\n{{='\\t'}}{{=buildAddedDesc(entity).join('\\n\\t')}}\n{{?}}{{? entity.data.fieldRemoved && entity.data.fieldRemoved.length > 0}}\n 删除字段:\n{{='\\t'}}{{=buildRemovedDesc(entity).join('\\n\\t')}}\n{{?}}\n{{~}}\n{{?}}", + "update": "{{\n let createEntities = it.changes.filter(function(row){return (row.opt==='add'&&row['type']==='entity');});\n let dropEntities = it.changes.filter(function(row){return (row.opt==='delete'&&row['type']==='entity');});\n let modifyEntities = it.changes.filter(function(row){return (row.opt==='update'&&row['type']==='entity');});\n \n function baseChangedDDL(beforeAfter){\n if(beforeAfter == null){\n return '';\n }\n let ret = [];\n let before = beforeAfter.before || {\"defKey\":\"\",\"defName\":\"\",\"comment\":\"\"};\n let after = beforeAfter.after || {\"defKey\":\"\",\"defName\":\"\",\"comment\":\"\"};\n if(before.defKey !== after.defKey){\n ret.push(`EXEC sp_rename '${before.defKey}','${after.defKey}'`);\n }\n let commentText = '';\n let commentChanged = false;\n if(before.defName !== after.defName){\n commentText = after.defName;\n commentChanged = true;\n }\n if(before.comment !== after.comment){\n commentChanged = true;\n if(commentText){\n commentText = (commentText+ ';'+after.comment)\n }else{\n commentText = after.comment\n }\n }\n if(commentChanged){\n let myText = `IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description','SCHEMA', 'dbo','TABLE', '${after.defKey}', NULL, NULL)) > 0)\n \\n\\tEXEC sp_updateextendedproperty 'MS_Description', '${commentText}','SCHEMA', 'dbo','TABLE', '${after.defKey}'\n \\nELSE\n \\n\\tEXEC sp_addextendedproperty 'MS_Description', '${commentText}', 'SCHEMA', 'dbo','TABLE', '${after.defKey}'\n `;\n ret.push(myText);\n /*ret.push('ALTER TABLE '+after.defKey+' COMMENT \\''+commentText+'\\'');*/\n }\n let baseText = '-- 基本信息:\\n';\n return baseText+ret.join(';\\n')+';';\n };\n \n function buildAddedDDL(entity){\n let ret = [];\n let fieldAdded = entity.data.fieldAdded||[];\n if(fieldAdded.length == 0){\n return '';\n }\n \n let firstDDL = `ALTER TABLE [dbo].[${entity.data.baseInfo.defKey}]`;\n for (let field of fieldAdded) { \n let ddlItem = `ADD [${field.defKey}] ${field.dbType}`;\n /*处理数据类型长度*/\n if(field.len>0){\n ddlItem += ('('+field.len);\n if(parseInt(field.scale)>0){\n ddlItem += (','+field.scale);\n }\n ddlItem += ')';\n }\n let defaultValue = field.defaultValue;\n defaultValue = (defaultValue==null)?\"\":(\"\"+defaultValue);\n if(defaultValue.length>0){\n ddlItem += (' DEFAULT ' + defaultValue);\n }\n if(field.notNull){\n ddlItem += ' NOT NULL';\n }\n ret.push(`${firstDDL} ${ddlItem}`);\n \n /*处理字段注释*/\n let fieldComments = [];\n if(field.defName != null &&field.defName.length>0){\n fieldComments.push(field.defName);\n }\n if(field.comment != null &&field.comment.length>0){\n fieldComments.push(field.comment);\n }\n let commentText = fieldComments.join(';');\n if(commentText != null && commentText.length > 0){\n let commentDDL = `EXEC sp_addextendedproperty 'MS_Description', N'${commentText}','SCHEMA', N'dbo','TABLE', N'${entity.data.baseInfo.defKey}','COLUMN', N'${field.defKey}'`;\n ret.push(commentDDL);\n }\n }\n return '\\n'+ret.join(';\\n');\n };\n \n function buildRemovedDDL(entity){\n let ret = [];\n let fieldRemoved = entity.data.fieldRemoved||[];\n if(fieldRemoved.length == 0){\n return '';\n }\n \n let firstDDL = `ALTER TABLE [dbo].[${entity.data.baseInfo.defKey}]`;\n for (let field of fieldRemoved) { \n ret.push(`${firstDDL} DROP COLUMN [${field.defKey}]`);\n }\n return '\\n'+ret.join(';\\n');\n };\n \n function buildModifiedDDL(entity){\n let ret = [];\n let fieldModified = entity.data.fieldModified||[];\n \n let firstDDL = `ALTER TABLE [dbo].[${entity.data.baseInfo.defKey}]`;\n for (let field of fieldModified) { \n let changeDDL = '';\n let before = field.before || {};\n let after = field.after || {};\n if(before.defKey === after.defKey){\n changeDDL += (' ALTER COLUMN ['+after.defKey+']');\n }else{\n let renameText = `EXEC sp_rename '[dbo].[${entity.data.baseInfo.defKey}].[${before.defKey}]','${after.defKey}','COLUMN';`;\n ret.push(renameText);\n continue;\n }\n changeDDL += (' '+after.dbType);\n if(after.len>0){\n changeDDL += ('('+after.len);\n if(parseInt(after.scale)>0){\n changeDDL += (','+after.scale);\n }\n changeDDL += ')';\n }\n let defaultValue = after.defaultValue;\n defaultValue = (defaultValue==null)?\"\":(\"\"+defaultValue);\n if(defaultValue.length>0){\n changeDDL += (' DEFAULT ' + defaultValue);\n }\n \n if(after.notNull){\n changeDDL += ' NOT NULL';\n }\n ret.push(`${firstDDL} ${changeDDL};`);\n }\n return ret;\n };\n}}\n{{? createEntities && createEntities.length > 0}}\n/* --------------- 创建表 --------------- */\n{{~ createEntities:entity}}\n{{=it.func.createDDL(entity.data,entity['type'])}}\n{{~}}\n{{?}}\n\n\n{{? dropEntities && dropEntities.length > 0}}\n/* --------------- 删除表 --------------- */\n{{~ dropEntities:entity}}\n{{=it.func.dropDDL(entity.data,entity['type'])}}\n{{~}}\n{{?}}\n\n\n{{? modifyEntities && modifyEntities.length > 0}}\n{{~ modifyEntities:entity}}\n/* --------------- 修改表 --------------- */\n-- 修改表:{{=entity.data.baseInfo.defKey}}[{{=entity.data.baseInfo.defName}}]\n{{=baseChangedDDL(entity.data.baseChanged)}}\n{{? entity.data.fieldModified && entity.data.fieldModified.length > 0}}\n-- 修改字段:\n{{=buildModifiedDDL(entity).join('\\n')}}\n{{?}}{{\n/*索引是否修改过*/\nlet indexChanged = entity.data.indexChanged;\n}}\n{{? indexChanged }}\n{{=it.func.indexRebuildDDL(entity.data.baseInfo,entity.data.newIndexes,entity.data.fullFields,entity['type'])}}\n{{?}}\n\n{{? entity.data.fieldAdded && entity.data.fieldAdded.length > 0}}\n-- 添加字段:\n{{=buildAddedDDL(entity)}};\n{{?}}\n\n{{? entity.data.fieldRemoved && entity.data.fieldRemoved.length > 0}}\n-- 删除字段:\n{{=buildRemovedDDL(entity)}};\n{{?}}\n{{~}}\n{{?}}" + }, + { + "applyFor": "DFBEC1DD-AA84-456E-BBF3-C95DD0DB2022", + "type": "dbDDL", + "createTable": "DROP TABLE IF EXISTS {{=it.entity.defKey}};\nCREATE TABLE {{=it.entity.defKey}}(\n{{ pkList = [] ; }}\n{{~it.entity.fields:field:index}}\n {{? field.primaryKey }}{{ pkList.push(field.defKey) }}{{?}}\n {{=field.defKey}} {{? field.autoIncrement}}SERIAL{{??}}{{=field.type}}{{?field.len>0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{?}}{{?}}{{= field.notNull ? ' NOT NULL' : '' }}{{= field.defaultValue ? it.func.join(' DEFAULT ',field.defaultValue,' ') : '' }}{{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i'+(after.defKey||'NULL'));\n }\n if(before.defName !== after.defName){\n ret.push('显示名称:'+(before.defName||'NULL')+'->'+(after.defName||'NULL'));\n }\n if(before.comment !== after.comment){\n ret.push('说明:'+(before.comment||'NULL')+'->'+(after.comment||'NULL'));\n }\n if(ret.length>0){\n return ' 基本信息:\\n\\t'+ret.join('\\n\\t');\n }\n return '';\n };\n \n function buildAddedDesc(entity){\n let ret = [];\n let fieldAdded = entity.data.fieldAdded||[];\n \n for (let field of fieldAdded) { \n let row = [];\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n \n function buildRemovedDesc(entity){\n let ret = [];\n let fieldRemoved = entity.data.fieldRemoved||[];\n \n for (let field of fieldRemoved) { \n let row = [];\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n \n function buildModifiedDesc(entity){\n let ret = [];\n let fieldModified = entity.data.fieldModified||[];\n \n for (let field1 of fieldModified) { \n let row = [];\n let field = field1.before;\n row.push(field.defKey+'['+field.defName+']');\n ret.push(row.join(\"\"))\n }\n return ret;\n };\n}}\n\n\n{{? createEntities && createEntities.length > 0}}\n/* --------------- 创建表 --------------- */\n{{~ createEntities:entity}}\n{{=entity.data.defKey}}[{{=entity.data.defName}}]\n{{~}}\n{{?}}\n\n\n{{? dropEntities && dropEntities.length > 0}}\n/* --------------- 删除表 --------------- */\n{{~ dropEntities:entity}}\n{{=entity.data.defKey}}[{{=entity.data.defName}}]\n{{~}}\n{{?}}\n\n{{? modifyEntities && modifyEntities.length > 0}}\n/* --------------- 修改表 --------------- */\n{{~ modifyEntities:entity}}\n{{=entity.data.baseInfo.defKey}}[{{=entity.data.baseInfo.defName}}]{{let changeText=baseChanged(entity.data.baseChanged);}}\n{{=baseChanged(entity.data.baseChanged)}}\n {{? entity.data.fieldModified && entity.data.fieldModified.length > 0}}\n 修改字段:\n {{='\\t'}}{{=buildModifiedDesc(entity).join('\\n\\t')}}{{?}}{{\n /*计算是否调整了属性*/\n let propAdded = entity.data.propAdded || [];\n let propRemoved = entity.data.propRemoved || [];\n let propModified = entity.data.propModified || [];\n let changed = propAdded.length>0 || propRemoved.length>0 || propModified.length>0;\n /*计算关联是否调整*/\n let refEntityAdd = entity.data.refEntityAdd || [];\n let refEntityRemoved = entity.data.refEntityRemoved || [];\n let relaArray = [];\n for (let rela of refEntityAdd) {\n relaArray.push('建立关联:'+rela.defKey+'['+rela.defName+']');\n }\n for (let rela of refEntityRemoved) {\n relaArray.push('解除关联:'+rela.defKey+'['+rela.defName+']');\n }\n /*索引是否修改过*/\n let indexChanged = entity.data.indexChanged;\n }}{{=indexChanged?'\\n\\t更改了索引':''}}{{=changed?'\\n\\t更改了属性':''}}{{=relaArray.length>0?('\\n\\t'+relaArray.join('\\n\\t')):''}}\n{{? entity.data.fieldAdded && entity.data.fieldAdded.length > 0}}\n 添加字段:\n{{='\\t'}}{{=buildAddedDesc(entity).join('\\n\\t')}}\n{{?}}{{? entity.data.fieldRemoved && entity.data.fieldRemoved.length > 0}}\n 删除字段:\n{{='\\t'}}{{=buildRemovedDesc(entity).join('\\n\\t')}}\n{{?}}\n{{~}}\n{{?}}", + "update": "{{\n let createEntities = it.changes.filter(function(row){return (row.opt==='add'&&row['type']==='entity');});\n let dropEntities = it.changes.filter(function(row){return (row.opt==='delete'&&row['type']==='entity');});\n let modifyEntities = it.changes.filter(function(row){return (row.opt==='update'&&row['type']==='entity');});\n \n function baseChangedDDL(beforeAfter){\n if(beforeAfter == null){\n return '';\n }\n let ret = [];\n let before = beforeAfter.before || {\"defKey\":\"\",\"defName\":\"\",\"comment\":\"\"};\n let after = beforeAfter.after || {\"defKey\":\"\",\"defName\":\"\",\"comment\":\"\"};\n if(before.defKey !== after.defKey){\n ret.push(`ALTER TABLE ${before.defKey} RENAME TO ${after.defKey}`);\n }\n let commentText = '';\n let commentChanged = false;\n if(before.defName !== after.defName){\n commentText = after.defName;\n commentChanged = true;\n }\n if(before.comment !== after.comment){\n commentChanged = true;\n if(commentText){\n commentText = (commentText+ ';'+after.comment)\n }else{\n commentText = after.comment\n }\n }\n if(commentChanged){\n let myText = `COMMENT ON TABLE ${after.defKey} IS '${commentText}'`;\n ret.push(myText);\n }\n let baseText = '-- 基本信息:\\n';\n return baseText+ret.join(';\\n')+';';\n };\n \n function buildAddedDDL(entity){\n let ret = [];\n let fieldAdded = entity.data.fieldAdded||[];\n if(fieldAdded.length == 0){\n return '';\n }\n \n let firstDDL = `ALTER TABLE ${entity.data.baseInfo.defKey}`;\n for (let field of fieldAdded) { \n let ddlItem = `ADD COLUMN ${field.defKey} ${field.dbType}`;\n /*处理数据类型长度*/\n if(field.len>0){\n ddlItem += ('('+field.len);\n if(parseInt(field.scale)>0){\n ddlItem += (','+field.scale);\n }\n ddlItem += ')';\n }\n if(field.notNull){\n ddlItem += ' NOT NULL';\n } \n let defaultValue = field.defaultValue;\n defaultValue = (defaultValue==null)?\"\":(\"\"+defaultValue);\n if(defaultValue.length>0){\n ddlItem += (' DEFAULT ' + defaultValue);\n }\n\n ret.push(`${firstDDL} ${ddlItem}`);\n \n /*处理字段注释*/\n let fieldComments = [];\n if(field.defName != null &&field.defName.length>0){\n fieldComments.push(field.defName);\n }\n if(field.comment != null &&field.comment.length>0){\n fieldComments.push(field.comment);\n }\n let commentText = fieldComments.join(';');\n if(commentText != null && commentText.length > 0){\n let commentDDL = `COMMENT ON COLUMN ${entity.data.baseInfo.defKey}.${field.defKey} IS '${commentText}'`;\n ret.push(commentDDL);\n }\n }\n return '\\n'+ret.join(';\\n');\n };\n \n function buildRemovedDDL(entity){\n let ret = [];\n let fieldRemoved = entity.data.fieldRemoved||[];\n if(fieldRemoved.length == 0){\n return '';\n }\n \n let firstDDL = `ALTER TABLE ${entity.data.baseInfo.defKey}`;\n for (let field of fieldRemoved) { \n ret.push(`${firstDDL} DROP COLUMN ${field.defKey}`);\n }\n return '\\n'+ret.join(';\\n');\n };\n \n function buildModifiedDDL(entity){\n let ret = [];\n let fieldModified = entity.data.fieldModified||[];\n \n let firstDDL = `ALTER TABLE ${entity.data.baseInfo.defKey}`;\n for (let field of fieldModified) { \n let before = field.before || {};\n let after = field.after || {};\n if(before.defKey !== after.defKey){\n let renameText = `ALTER TABLE ${entity.data.baseInfo.defKey} RENAME COLUMN ${before.defKey} TO ${after.defKey};`;\n ret.push(renameText);\n }\n /*如果没有变化,则不生成变更语句*/\n if(before.dbType === after.dbType \n && before['len'] === after['len'] \n && before.scale === after.scale\n && before.primaryKey === after.primaryKey\n && before.notNull === after.notNull\n && before.autoIncrement === after.autoIncrement\n && before.defaultValue === after.defaultValue){\n continue;\n }\n if(before.dbType !== after.dbType || before.len !== after.len || before.scale !== after.scale){\n let dbTypeDDL = `ALTER TABLE ${entity.data.baseInfo.defKey} ALTER COLUMN ${after.defKey} TYPE ${before.dbType}`;\n if(after.len>0){\n dbTypeDDL += ('('+after.len);\n if(parseInt(after.scale)>0){\n dbTypeDDL += (','+after.scale);\n }\n dbTypeDDL += ')';\n }\n ret.push(dbTypeDDL+';');\n }\n \n if(before.defaultValue !== after.defaultValue){\n let defaultDDL = '';\n let defaultValue = after.defaultValue;\n defaultValue = (defaultValue==null)?\"NULL\":(\"\"+defaultValue);\n if(defaultValue.length>0){\n defaultDDL += ('SET DEFAULT ' + defaultValue);\n }\n let defaultTpl = `ALTER TABLE ${entity.data.baseInfo.defKey} ALTER COLUMN ${defaultDDL};`;\n ret.push(defaultTpl);\n }\n \n if(before.notNull !== after.notNull){\n let notNullDDL= 'SET NULL';\n if(after.notNull){\n let notNullDDL= 'SET NOT NULL';\n }\n let notNullTpl = `ALTER TABLE ${entity.data.baseInfo.defKey} ALTER COLUMN ${notNullDDL};`;\n ret.push(notNullTpl);\n }\n }\n return ret;\n };\n}}\n{{? createEntities && createEntities.length > 0}}\n/* --------------- 创建表 --------------- */\n{{~ createEntities:entity}}\n{{=it.func.createDDL(entity.data,entity['type'])}}\n{{~}}\n{{?}}\n\n\n{{? dropEntities && dropEntities.length > 0}}\n/* --------------- 删除表 --------------- */\n{{~ dropEntities:entity}}\n{{=it.func.dropDDL(entity.data,entity['type'])}}\n{{~}}\n{{?}}\n\n\n{{? modifyEntities && modifyEntities.length > 0}}\n{{~ modifyEntities:entity}}\n/* --------------- 修改表 --------------- */\n-- 修改表:{{=entity.data.baseInfo.defKey}}[{{=entity.data.baseInfo.defName}}]\n{{=baseChangedDDL(entity.data.baseChanged)}}\n{{? entity.data.fieldModified && entity.data.fieldModified.length > 0}}\n-- 修改字段:\n{{=buildModifiedDDL(entity).join('\\n')}}\n{{?}}{{\n/*索引是否修改过*/\nlet indexChanged = entity.data.indexChanged;\n}}\n{{? indexChanged }}\n-- 索引重建\n{{=it.func.indexRebuildDDL(entity.data.baseInfo,entity.data.newIndexes,entity.data.fullFields,entity['type'])}}\n{{?}}\n\n{{? entity.data.fieldAdded && entity.data.fieldAdded.length > 0}}\n-- 添加字段:\n{{=buildAddedDDL(entity)}};\n{{?}}\n\n{{? entity.data.fieldRemoved && entity.data.fieldRemoved.length > 0}}\n-- 删除字段:\n{{=buildRemovedDDL(entity)}};\n{{?}}\n{{~}}\n{{?}}" + }, + { + "type": "dbDDL", + "applyFor": "89504F5D-94BF-4C9E-8B2E-44F37305FED5", + "createTable": "CREATE TABLE {{=it.entity.defKey}}(\n{{ pkList = [] ; }}\n{{~it.entity.fields:field:index}}\n {{? field.primaryKey }}{{ pkList.push(field.defKey) }}{{?}}\n {{=field.defKey}} {{=field.type}}{{?field.len>0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{?}}{{= field.notNull ? ' NOT NULL' : '' }}{{= field.autoIncrement ? '' : '' }}{{= field.defaultValue ? it.func.join(' DEFAULT ',field.defaultValue,' ') : '' }}{{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{?}}{{= field.notNull ? ' NOT NULL' : '' }}{{= field.autoIncrement ? '' : '' }}{{= field.defaultValue ? it.func.join(' DEFAULT ','('+field.defaultValue+')',' ') : '' }}{{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{?}}{{= field.notNull ? ' NOT NULL' : '' }}{{= field.autoIncrement ? ' AUTO_INCREMENT' : '' }}{{= field.defaultValue ? it.func.join(' DEFAULT ',field.defaultValue,' ') : '' }}{{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{= field.defaultValue ? it.func.join(' DEFAULT ',field.defaultValue,' ') : '' }}{{?}}{{= field.notNull ? ' NOT NULL' : '' }}{{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i0}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{?}}{{=')'}}{{?}} {{= field.notNull ? 'NOT NULL' : '' }} {{= field.autoIncrement ? 'AUTOINCREMENT' : '' }} {{= field.defaultValue ? it.func.join('DEFAULT',field.defaultValue,' ') : '' }}{{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }} --{{=it.func.join(field.defName,field.comment,';')}}\n{{~}}\n{{? pkList.length >0 }}\n PRIMARY KEY ({{~pkList:pkName:i}}{{= pkName }}{{= i0只有为decimal类型或double类型时才保留长度和小数的位数*/\n{{~it.entity.fields:field:index}}\n {{=it.func.lowerCase(field.defKey)}} {{=it.func.lowerCase(field.dbType)=='varchar'||it.func.lowerCase(field.dbType)=='char'||it.func.lowerCase(field.dbType)=='text'||it.func.lowerCase(field.dbType)=='date'||it.func.lowerCase(field.dbType)=='datetime' ? 'string':it.func.lowerCase(field.dbType)=='tinyint unsigned'||it.func.lowerCase(field.dbType)=='bit'||it.func.lowerCase(field.dbType)=='integer'||it.func.lowerCase(field.dbType)=='tinyint'||it.func.lowerCase(field.dbType)=='smallint'||it.func.lowerCase(field.dbType)=='mediumint' ? 'int':it.func.lowerCase(field.dbType)=='int unsigned' ? 'bigint':it.func.lowerCase(field.dbType)}}{{?field.len>0&&(it.func.lowerCase(field.dbType)=='decimal'||it.func.lowerCase(field.dbType)=='double')}}{{='('}}{{=field.len}}{{?field.scale>0}}{{=','}}{{=field.scale}}{{=')'}}{{?}}{{?}} comment '{{=it.func.join(field.defName,field.comment,'')}}' {{= index < it.entity.fields.length-1 ? ',' : ( pkList.length>0 ? ',' :'' ) }}\n{{~}}\n{{? pkList.length >0 }}\n{{?}}\n)\n{{\n let partitionedBy = it.entity.properties['partitioned by'];\n partitionedBy = partitionedBy?partitionedBy:'请在扩展属性中配置[partitioned by]属性';\n}}\ncomment '{{=it.func.join(it.entity.defName,';') }}'\n/**是否分区表,分区字段名和字段注释自定义*/\n[partitioned by {{=partitionedBy}}]\n/**文件存储格式自定义*/\n[stored as orc]\n/**hdfs上的地址自定义*/\n[location xxx]\n;", + "createView": "", + "deleteTable": "", + "createIndex": "", + "deleteIndex": "", + "message": "", + "update": "" + }, + { + "applyFor": "B91D99E0-9B7C-416C-8737-B760957DAF09", + "type": "appCode", + "content": "{{\n var today=new Date();\n var fullYear=today.getFullYear();\n var month=today.getMonth() + 1<10?\"0\"+today.getMonth():today.getMonth();\n var days=today.getDate()<10?\"0\"+today.getDate():today.getDate();\n var hours = today.getHours()<10?\"0\"+today.getHours():today.getHours(); \n\tvar minutes = today.getMinutes()<10?\"0\"+today.getMinutes():today.getMinutes(); \n\tvar seconds = today.getSeconds()<10?\"0\"+today.getSeconds():today.getSeconds(); \n}}\n// Package models {{=it.func.join(it.entity.defName,it.entity.comment,',')}}\n// author : http://www.liyang.love\n// date : {{=fullYear}}-{{=month}}-{{=days}} {{=hours}}:{{=minutes}}\n// desc : {{=it.func.join(it.entity.defName,it.entity.comment,',')}}\npackage models\n\n$blankline\n\n// {{=it.func.camel(it.entity.defKey,true) }} {{=it.func.join(it.entity.defName,it.entity.comment,',')}}。\n// 说明:{{=it.entity.comment}}\n// 表名:{{=it.entity.defKey}}\n// group: {{=it.func.camel(it.entity.defKey,true) }}\n// obsolete:\n// appliesto:go 1.8+;\n// namespace:hongmouer.his.models.{{=it.func.camel(it.entity.defKey,true) }}\n// assembly: hongmouer.his.models.go\n// class:HongMouer.HIS.Models.{{=it.func.camel(it.entity.defKey,true) }}\n// version:{{=fullYear}}-{{=month}}-{{=days}} {{=hours}}:{{=minutes}}\ntype {{=it.func.camel(it.entity.defKey,true) }} struct {\n {{~it.entity.fields:field:index}}\n {{=formatGoLang(it.func.camel(field.defKey,true),null,field,it.entity.fields,null,1)}} {{=formatGoLang(field.type,\"type\",field,it.entity.fields,10,3)}} `gorm:\"column:{{=field.primaryKey?\"primaryKey;\":\"\"}}{{=field.defKey}}\" json:\"{{=it.func.camel(field.defKey,true)}}\"` {{=formatGoLang(\"gorm:column:\"+field.defKey+\" json:\"+it.func.camel(field.defKey,true),null,field,it.entity.fields,null,2)}} //type:{{=formatGoLang(field.type,\"type\",field,it.entity.fields,null,3)}} comment:{{=formatGoLang(it.func.join(field.defName,field.comment,';'),\"defName\",field,it.entity.fields,null,4)}} version:{{=fullYear}}-{{=month}}-{{=days}} {{=hours}}:{{=minutes}}\n {{~}}\n}\n\n\n$blankline\n// TableName 表名:{{=it.entity.defKey}},{{=it.entity.defName}}。\n// 说明:{{=it.entity.comment}}\nfunc (ZentaoUserInfo) TableName() string {\n\treturn \"{{=it.entity.defKey}}\"\n}\n\n{{\n\nfunction formatGoLang(str, fieldName, field, fileds, emptLength, isFiled) {\n var maxLength = 0;\n\n if (isFiled == 1) {\n for (var i = 0; i < fileds.length; i++) {\n if (getBlength(it.func.camel(fileds[i].defKey, true)) > maxLength) {\n maxLength = getBlength(it.func.camel(fileds[i].defKey, true)) + 2;\n }\n }\n } else if (isFiled == 2) {\n for (var i = 0; i < fileds.length; i++) {\n var newStr = \"gorm:column:\" + fileds[i].defKey + \" json:\" + it.func.camel(fileds[i].defKey, true);\n if (getBlength(newStr) > maxLength) {\n maxLength = getBlength(newStr) + 2;\n }\n }\n var empt = \"\";\n var strLength = getBlength(str);\n if (field.primaryKey) {\n strLength += getBlength(\"primaryKey;\");\n }\n for (var j = 0; j < maxLength - strLength; j++) {\n empt += ' ';\n }\n return empt;\n } else if (isFiled == 3) {\n /*获取某个字段的最大长度*/\n for (var i = 0; i < fileds.length; i++) {\n var newStr = eval(\"fileds[\" + i + \"].\" + fieldName);\n if (getBlength(newStr) > maxLength) {\n maxLength = getBlength(newStr) + 1;\n }\n }\n } else if (isFiled == 4) {\n /*获取某个字段的最大长度*/\n for (var i = 0; i < fileds.length; i++) {\n var newStr = fileds[i].comment + \";\" + fileds[i].defName;\n if (getBlength(newStr) > maxLength) {\n maxLength = getBlength(newStr) + 1;\n }\n }\n }\n else {\n maxLength = emptLength;\n }\n\n var strLength = getBlength(str);\n for (var j = 0; j < maxLength - strLength; j++) {\n str += ' ';\n }\n return str;\n}\n\nfunction getBlength(str) {\n var n = 0;\n for (var i = str.length; i--;) {\n n += str.charCodeAt(i) > 255 ? 2 : 1;\n }\n return n;\n} \n\n}}" + } + ], + "generatorDoc": { + "docTemplate": "" + }, + "relationFieldSize": "15", + "uiHint": [ + { + "defKey": "Input", + "defName": "普通输入框", + "id": "642D2E0A-8846-4549-BE56-8C0473F26EDE" + }, + { + "defKey": "Select", + "defName": "下拉输入框", + "id": "FB111359-2B73-4443-926C-08A98E446448" + }, + { + "defKey": "CheckBox", + "defName": "复选框", + "id": "0CB8A6C9-1115-4FC0-B51E-5C028065082F" + }, + { + "defKey": "RadioBox", + "defName": "单选框", + "id": "5C04987A-260F-4B7C-A5D5-22A181AAE9CA" + }, + { + "defKey": "Double", + "defName": "小数输入", + "id": "8D5BAFE4-E15C-4707-A047-8EE59C58E70F" + }, + { + "defKey": "Integer", + "defName": "整数输入", + "id": "9999AF2A-A44E-415C-A2DC-D7C613BD0073" + }, + { + "defKey": "Money", + "defName": "金额输入", + "id": "2B0C3D0C-7BAF-4B36-81AD-9362B5E5DC2E" + }, + { + "defKey": "Date", + "defName": "日期输入", + "id": "E4D94E14-F695-487F-AFC2-4D888009B7DA" + }, + { + "defKey": "DataYearMonth", + "defName": "年月输入", + "id": "936927E3-DD2D-4096-87FD-074CDE278D59" + }, + { + "defKey": "Text", + "defName": "长文本输入", + "id": "D89DD4F1-ADAC-4469-BF8D-B3FF41AE7963" + }, + { + "defKey": "RichText", + "defName": "富文本输入", + "id": "C134EB1F-4CFF-49E0-882F-2C6FB275CB20" + } + ], + "headers": [ + { + "refKey": "defKey", + "hideInGraph": false + }, + { + "refKey": "defName", + "hideInGraph": false + }, + { + "refKey": "primaryKey", + "hideInGraph": false + }, + { + "refKey": "notNull", + "hideInGraph": true + }, + { + "refKey": "autoIncrement", + "hideInGraph": true + }, + { + "refKey": "domain", + "hideInGraph": true + }, + { + "refKey": "type", + "hideInGraph": false + }, + { + "refKey": "len", + "hideInGraph": false + }, + { + "refKey": "scale", + "hideInGraph": false + }, + { + "refKey": "comment", + "hideInGraph": true + }, + { + "refKey": "refDict", + "hideInGraph": true + }, + { + "refKey": "defaultValue", + "hideInGraph": true + }, + { + "refKey": "isStandard", + "hideInGraph": false + }, + { + "refKey": "uiHint", + "hideInGraph": true + }, + { + "refKey": "extProps", + "hideInGraph": true + } + ] + }, + "entities": [], + "views": [], + "dicts": [ + { + "defKey": "Gender", + "defName": "性别", + "intro": "", + "items": [ + { + "defKey": "M", + "defName": "男", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "1", + "id": "3622D417-DA1A-408F-BEE1-11D328D534A0" + }, + { + "defKey": "F", + "defName": "女", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "2", + "id": "380A0790-64A7-481E-831C-32F7BEE1502B" + }, + { + "defKey": "U", + "defName": "未知", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "3", + "id": "FA239F4D-1276-40D9-B230-F66BD35C3C27" + } + ], + "id": "BF9E20E0-80D3-486D-BD58-5FADCF3E4A1D" + }, + { + "defKey": "Political", + "defName": "政治面貌", + "intro": "", + "items": [ + { + "defKey": "10", + "defName": "共青团员", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "61F3145A-7599-4CCB-B298-D5EE788107BE" + }, + { + "defKey": "20", + "defName": "中共党员", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "ED16D25A-AB2F-4FA0-BB48-2B9031FA28C4" + }, + { + "defKey": "30", + "defName": "民主党派", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "0FB7974A-AE11-438F-86E0-B163316F9272" + }, + { + "defKey": "40", + "defName": "群众", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "7D247234-7E97-45B1-8C56-4A17A370854A" + }, + { + "defKey": "90", + "defName": "未知", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "DA65D752-AF04-4A11-81D8-14A38692A64A" + } + ], + "id": "06EED564-BBA9-4747-8D73-AF809A330CB8" + }, + { + "defKey": "Marital", + "defName": "婚姻状况", + "intro": "婚姻状况的码表", + "items": [ + { + "defKey": "UNMARRIED", + "defName": "未婚", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "1", + "id": "20EE81BC-74EE-47DA-A56F-9663B23F44BD" + }, + { + "defKey": "MARRIED", + "defName": "已婚", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "2", + "id": "4DCA10A8-417E-4A8D-BDF6-0A278C060ADC" + }, + { + "defKey": "WIDOWED", + "defName": "丧偶", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "3", + "id": "826062A7-057C-4892-B338-06459F5B808D" + }, + { + "defKey": "DIVORCE", + "defName": "离婚", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "4", + "id": "B23200B4-5E59-4F5E-A779-D981A040FA32" + }, + { + "defKey": "UNSPECIFIED", + "defName": "未说明", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "5", + "id": "A7928FE2-349A-4702-9682-2EF7205E077B" + } + ], + "id": "EA1587B7-3954-437A-BFE0-FCB0453BCABA" + }, + { + "defKey": "StudentStatus", + "defName": "学生状态", + "intro": "", + "items": [ + { + "defKey": "Normal", + "defName": "正常", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "1", + "id": "E9CA1CC9-8851-4F6B-86BA-B9CF0E44EB73" + }, + { + "defKey": "Graduated", + "defName": "毕业", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "2", + "id": "DEC51D7C-99DF-430C-817D-0499862D3CCC" + }, + { + "defKey": "Studied", + "defName": "肄业", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "3", + "id": "8853D6B6-75D3-4479-9006-FC731CD85B20" + }, + { + "defKey": "Stop", + "defName": "休学", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "sort": "4", + "id": "C74BA8CF-1DC6-4C79-BAAC-F11EB9C6AF01" + } + ], + "id": "4642BC5F-02EE-4E17-A60C-CF22F86A0282" + }, + { + "defKey": "GBNation", + "defName": "民族", + "intro": "", + "items": [ + { + "defKey": "01", + "defName": "汉族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "9224DF53-F7C0-447D-B8ED-0A39F799EE19" + }, + { + "defKey": "02", + "defName": "蒙古族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "D57818E3-9206-45BB-AE79-27C64A4AB80F" + }, + { + "defKey": "03", + "defName": "回族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "0A1A3CA9-6D68-4E15-8BD0-9A2FF428D804" + }, + { + "defKey": "04", + "defName": "藏族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "7CC6B6BE-47EA-460E-ACFA-C377468DEA11" + }, + { + "defKey": "05", + "defName": "维吾尔族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "A666D51F-D249-4FAC-B1F3-78C371836CB3" + }, + { + "defKey": "06", + "defName": "苗族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "A0C9E1BA-D87B-4695-ADFA-287FDA32BB5A" + }, + { + "defKey": "07", + "defName": "彝族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "149B5B9E-C1D1-4790-8CCF-0ED5F4B25172" + }, + { + "defKey": "08", + "defName": "壮族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "F9A3E65C-BF4D-4C6B-ADB7-8C9CF0487360" + }, + { + "defKey": "09", + "defName": "布依族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "14F17DE4-E96A-460B-98A6-F84EC8CF9885" + }, + { + "defKey": "10", + "defName": "朝鲜族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "8A81AB18-B1BF-4797-A6E5-DEDB2C6566B0" + }, + { + "defKey": "11", + "defName": "满族", + "intro": "", + "parentKey": "", + "enabled": true, + "attr1": "", + "attr2": "", + "attr3": "", + "id": "7D460947-FBD2-4E4D-8366-3B38DCAF09D1" + } + ], + "id": "115EDEFC-0323-410E-81AB-CCAB8879837A" + }, + { + "defKey": "GradeLevel", + "defName": "受教育程度", + "sort": "", + "intro": "", + "items": [], + "id": "9E7C9788-B805-4C7D-8531-FD1D9DC79B05" + } + ], + "viewGroups": [], + "dataTypeMapping": { + "referURL": "", + "mappings": [ + { + "defKey": "string", + "defName": "字串", + "id": "FC9790A7-36B8-4A48-8F9A-BC1042BCFE64", + "29D1CE08-4C35-4D2D-AAA9-23D93305B52E": "VARCHAR", + "A4E23CB7-BB01-4BD1-9F71-F73F3E15A542": "VARCHAR2", + "BFC87171-C74F-494A-B7C2-76B9C55FACC9": "VARCHAR", + "DFBEC1DD-AA84-456E-BBF3-C95DD0DB2022": "VARCHAR", + "89504F5D-94BF-4C9E-8B2E-44F37305FED5": "VARCHAR", + "0BBCABA5-B8E4-41B0-B8E4-8F5EA6029307": "VARCHAR2", + "592C7013-143D-4E7B-AF64-0D7BF1E28230": "VARCHAR", + "77BD85E5-9D0D-4096-8427-CBA306FC9C6A": "VARCHAR", + "11D1FB71-A587-4217-89BA-611B8A1F83E0": "STRING", + "B363BE0B-F852-49B8-9B2E-F6D2174DEAC1": "TEXT", + "797A1496-D649-4261-89B4-544132EC3F36": "String", + "895CFD1D-4273-4D32-A2C4-CAC70200AB5B": "String", + "A2EE7B4A-CE62-4290-B00C-B26C1BF18073": "String", + "F3AC2415-E86B-40C6-9FEB-F4B7937D2C30": "string", + "81CCA482-3F4D-4EAC-8CF9-F5E7BC098AD2": "string", + "B91D99E0-9B7C-416C-8737-B760957DAF09": "string" + }, + { + "defKey": "double", + "defName": "小数", + "id": "1A0BDC09-0792-4174-9E8E-80BE8DF44B8E", + "29D1CE08-4C35-4D2D-AAA9-23D93305B52E": "DECIMAL", + "A4E23CB7-BB01-4BD1-9F71-F73F3E15A542": "DECIMAL", + "BFC87171-C74F-494A-B7C2-76B9C55FACC9": "DECIMAL", + "DFBEC1DD-AA84-456E-BBF3-C95DD0DB2022": "NUMERIC", + "89504F5D-94BF-4C9E-8B2E-44F37305FED5": "DECIMAL", + "0BBCABA5-B8E4-41B0-B8E4-8F5EA6029307": "DECIMAL", + "592C7013-143D-4E7B-AF64-0D7BF1E28230": "NUMERIC", + "77BD85E5-9D0D-4096-8427-CBA306FC9C6A": "NUMERIC", + "11D1FB71-A587-4217-89BA-611B8A1F83E0": "DOUBLE", + "B363BE0B-F852-49B8-9B2E-F6D2174DEAC1": "REAL", + "797A1496-D649-4261-89B4-544132EC3F36": "Double", + "895CFD1D-4273-4D32-A2C4-CAC70200AB5B": "Double", + "A2EE7B4A-CE62-4290-B00C-B26C1BF18073": "Double", + "F3AC2415-E86B-40C6-9FEB-F4B7937D2C30": "decimal", + "81CCA482-3F4D-4EAC-8CF9-F5E7BC098AD2": "double", + "B91D99E0-9B7C-416C-8737-B760957DAF09": "*float64" + }, + { + "defKey": "int", + "defName": "整数", + "id": "1D764C4A-6F9F-421E-B11A-6F3E23B51811", + "29D1CE08-4C35-4D2D-AAA9-23D93305B52E": "INT", + "A4E23CB7-BB01-4BD1-9F71-F73F3E15A542": "INT", + "BFC87171-C74F-494A-B7C2-76B9C55FACC9": "INT", + "DFBEC1DD-AA84-456E-BBF3-C95DD0DB2022": "INTEGER", + "89504F5D-94BF-4C9E-8B2E-44F37305FED5": "INT", + "0BBCABA5-B8E4-41B0-B8E4-8F5EA6029307": "INTEGER", + "592C7013-143D-4E7B-AF64-0D7BF1E28230": "INTEGER", + "77BD85E5-9D0D-4096-8427-CBA306FC9C6A": "INT4", + "11D1FB71-A587-4217-89BA-611B8A1F83E0": "INT", + "B363BE0B-F852-49B8-9B2E-F6D2174DEAC1": "INTEGER", + "797A1496-D649-4261-89B4-544132EC3F36": "Integer", + "895CFD1D-4273-4D32-A2C4-CAC70200AB5B": "Integer", + "A2EE7B4A-CE62-4290-B00C-B26C1BF18073": "Integer", + "F3AC2415-E86B-40C6-9FEB-F4B7937D2C30": "float", + "81CCA482-3F4D-4EAC-8CF9-F5E7BC098AD2": "int", + "B91D99E0-9B7C-416C-8737-B760957DAF09": "*int" + }, + { + "defKey": "date", + "defName": "日期", + "id": "89D69E81-EA34-42EE-9FA2-93B8BD27E098", + "29D1CE08-4C35-4D2D-AAA9-23D93305B52E": "DATETIME", + "A4E23CB7-BB01-4BD1-9F71-F73F3E15A542": "DATE", + "BFC87171-C74F-494A-B7C2-76B9C55FACC9": "DATETIME", + "DFBEC1DD-AA84-456E-BBF3-C95DD0DB2022": "TIMESTAMP", + "89504F5D-94BF-4C9E-8B2E-44F37305FED5": "DATE", + "0BBCABA5-B8E4-41B0-B8E4-8F5EA6029307": "DATE", + "592C7013-143D-4E7B-AF64-0D7BF1E28230": "DATE", + "77BD85E5-9D0D-4096-8427-CBA306FC9C6A": "DATE", + "11D1FB71-A587-4217-89BA-611B8A1F83E0": "DATETIME", + "B363BE0B-F852-49B8-9B2E-F6D2174DEAC1": "NUMERIC", + "797A1496-D649-4261-89B4-544132EC3F36": "Date", + "895CFD1D-4273-4D32-A2C4-CAC70200AB5B": "Date", + "A2EE7B4A-CE62-4290-B00C-B26C1BF18073": "Date", + "F3AC2415-E86B-40C6-9FEB-F4B7937D2C30": "DateTime", + "81CCA482-3F4D-4EAC-8CF9-F5E7BC098AD2": "timestamp", + "B91D99E0-9B7C-416C-8737-B760957DAF09": "*time.Time" + }, + { + "defKey": "bytes", + "defName": "二进制", + "id": "D516E75B-90F5-4741-B9B3-A186A263F04C", + "29D1CE08-4C35-4D2D-AAA9-23D93305B52E": "BLOB", + "A4E23CB7-BB01-4BD1-9F71-F73F3E15A542": "BLOB", + "BFC87171-C74F-494A-B7C2-76B9C55FACC9": "VARBINARY", + "DFBEC1DD-AA84-456E-BBF3-C95DD0DB2022": "BYTEA", + "89504F5D-94BF-4C9E-8B2E-44F37305FED5": "BLOB", + "0BBCABA5-B8E4-41B0-B8E4-8F5EA6029307": "BLOB", + "592C7013-143D-4E7B-AF64-0D7BF1E28230": "BYTEA", + "77BD85E5-9D0D-4096-8427-CBA306FC9C6A": "BYTEA", + "11D1FB71-A587-4217-89BA-611B8A1F83E0": "BINARY", + "B363BE0B-F852-49B8-9B2E-F6D2174DEAC1": "NONE", + "797A1496-D649-4261-89B4-544132EC3F36": "byte[]", + "895CFD1D-4273-4D32-A2C4-CAC70200AB5B": "byte[]", + "A2EE7B4A-CE62-4290-B00C-B26C1BF18073": "byte[]", + "F3AC2415-E86B-40C6-9FEB-F4B7937D2C30": "binary", + "81CCA482-3F4D-4EAC-8CF9-F5E7BC098AD2": "binary", + "B91D99E0-9B7C-416C-8737-B760957DAF09": "[]byte" + }, + { + "defKey": "largeText", + "defName": "大文本", + "id": "B17BDED3-085F-40E1-9019-3B79CF2BF075", + "29D1CE08-4C35-4D2D-AAA9-23D93305B52E": "TEXT", + "A4E23CB7-BB01-4BD1-9F71-F73F3E15A542": "CLOB", + "BFC87171-C74F-494A-B7C2-76B9C55FACC9": "TEXT", + "DFBEC1DD-AA84-456E-BBF3-C95DD0DB2022": "TEXT", + "89504F5D-94BF-4C9E-8B2E-44F37305FED5": "CLOB", + "0BBCABA5-B8E4-41B0-B8E4-8F5EA6029307": "CLOB", + "592C7013-143D-4E7B-AF64-0D7BF1E28230": "TEXT", + "77BD85E5-9D0D-4096-8427-CBA306FC9C6A": "TEXT", + "11D1FB71-A587-4217-89BA-611B8A1F83E0": "STRING", + "B363BE0B-F852-49B8-9B2E-F6D2174DEAC1": "TEXT", + "797A1496-D649-4261-89B4-544132EC3F36": "String", + "895CFD1D-4273-4D32-A2C4-CAC70200AB5B": "String", + "A2EE7B4A-CE62-4290-B00C-B26C1BF18073": "String", + "F3AC2415-E86B-40C6-9FEB-F4B7937D2C30": "string", + "81CCA482-3F4D-4EAC-8CF9-F5E7BC098AD2": "string", + "B91D99E0-9B7C-416C-8737-B760957DAF09": "string" + } + ] + }, + "domains": [ + { + "defKey": "DefaultString", + "defName": "默认字串", + "applyFor": "FC9790A7-36B8-4A48-8F9A-BC1042BCFE64", + "len": 255, + "scale": "", + "uiHint": "", + "id": "9092C4E0-1A54-4859-ABBB-5B62DBC27573" + }, + { + "defKey": "IdOrKey", + "defName": "主键标识", + "applyFor": "FC9790A7-36B8-4A48-8F9A-BC1042BCFE64", + "len": 32, + "scale": "", + "uiHint": "", + "id": "16120F75-6AA7-4483-868D-F07F511BB081" + }, + { + "defKey": "Name", + "defName": "名称", + "applyFor": "FC9790A7-36B8-4A48-8F9A-BC1042BCFE64", + "len": 90, + "scale": "", + "uiHint": "", + "id": "54611CCC-CA4B-42E1-9F32-4944C85B85A6" + }, + { + "defKey": "Int", + "defName": "整数", + "applyFor": "1D764C4A-6F9F-421E-B11A-6F3E23B51811", + "len": "", + "scale": "", + "uiHint": "", + "id": "6BC8F04B-6CFA-4995-98D3-318F5CDD774E" + }, + { + "defKey": "Double", + "defName": "小数", + "applyFor": "1A0BDC09-0792-4174-9E8E-80BE8DF44B8E", + "len": 24, + "scale": 6, + "uiHint": "", + "id": "FF4459C5-6B45-4DBF-8FC0-E06239BC05B4" + }, + { + "defKey": "Money", + "defName": "金额", + "applyFor": "1A0BDC09-0792-4174-9E8E-80BE8DF44B8E", + "len": 24, + "scale": 6, + "uiHint": "", + "id": "C3B1681B-99F9-4818-9E80-DE1652A51D85" + }, + { + "defKey": "DateTime", + "defName": "日期时间", + "applyFor": "89D69E81-EA34-42EE-9FA2-93B8BD27E098", + "len": "", + "scale": "", + "uiHint": "", + "id": "7CFFA0D3-6A93-4DDC-BC10-DF21211064DC" + }, + { + "defKey": "YesNo", + "defName": "是否", + "applyFor": "FC9790A7-36B8-4A48-8F9A-BC1042BCFE64", + "len": "1", + "scale": "", + "uiHint": "", + "id": "6F7C1C5C-D159-41E6-BF9D-54DEEFA79AFF" + }, + { + "defKey": "Dict", + "defName": "数据字典", + "applyFor": "FC9790A7-36B8-4A48-8F9A-BC1042BCFE64", + "len": "32", + "scale": "", + "uiHint": "", + "id": "73FD2BAD-2358-4336-B96D-45DC897BD792" + }, + { + "defKey": "DescText", + "defName": "描述文本", + "applyFor": "FC9790A7-36B8-4A48-8F9A-BC1042BCFE64", + "len": "900", + "scale": "", + "uiHint": "", + "id": "3E948CEC-3070-472C-AF92-F3CA11EC9D15" + } + ], + "diagrams": [], + "standardFields": [ + { + "defKey": "personInfo", + "defName": "个人基本信息要素", + "fields": [ + { + "defKey": "ID_CARD_NO", + "defName": "身份证号", + "comment": "", + "type": "VARCHAR", + "len": "60", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "9092C4E0-1A54-4859-ABBB-5B62DBC27573", + "refDict": "", + "uiHint": "", + "id": "A64A91C8-A41F-4113-92FB-7563D7EF054D" + }, + { + "defKey": "MOBILE_PHONE", + "defName": "手机号", + "comment": "", + "type": "VARCHAR", + "len": "60", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "9092C4E0-1A54-4859-ABBB-5B62DBC27573", + "refDict": "", + "uiHint": "", + "id": "479DA2AB-1974-411A-A81E-92FB939E75EB" + }, + { + "defKey": "GENDER", + "defName": "性别", + "comment": "", + "type": "VARCHAR", + "len": "32", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "73FD2BAD-2358-4336-B96D-45DC897BD792", + "refDict": "BF9E20E0-80D3-486D-BD58-5FADCF3E4A1D", + "uiHint": "", + "id": "48473E29-6594-4912-AADE-C8AB44FCA3E9" + }, + { + "defKey": "BIRTH", + "defName": "出生日期", + "comment": "", + "type": "DATETIME", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "7CFFA0D3-6A93-4DDC-BC10-DF21211064DC", + "refDict": "", + "uiHint": "", + "id": "2BD3D2EE-2411-49A6-983D-84B81057312F" + }, + { + "defKey": "AVATAR", + "defName": "头像", + "comment": "", + "type": "VARCHAR", + "len": "60", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "9092C4E0-1A54-4859-ABBB-5B62DBC27573", + "refDict": "", + "uiHint": "", + "id": "FDD67CEE-4B52-4BD1-A1A3-9C5EBC6037E6" + }, + { + "defKey": "HEIGHT", + "defName": "身高", + "comment": "", + "type": "INT", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "6BC8F04B-6CFA-4995-98D3-318F5CDD774E", + "refDict": "", + "uiHint": "", + "id": "CAAA0E79-41A1-4758-B481-D171168C4D68" + }, + { + "defKey": "WEIGHT", + "defName": "体重", + "comment": "", + "type": "INT", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "6BC8F04B-6CFA-4995-98D3-318F5CDD774E", + "refDict": "", + "uiHint": "", + "id": "575482CE-64A6-4CB9-99DC-8E126D190AAA" + }, + { + "defKey": "NATION", + "defName": "名族", + "comment": "", + "type": "VARCHAR", + "len": "32", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "73FD2BAD-2358-4336-B96D-45DC897BD792", + "refDict": "115EDEFC-0323-410E-81AB-CCAB8879837A", + "uiHint": "", + "id": "15B0D75D-0B97-4985-A816-D0EAFA90446B" + }, + { + "defKey": "POLITICAL", + "defName": "政治面貌", + "comment": "", + "type": "VARCHAR", + "len": "32", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "73FD2BAD-2358-4336-B96D-45DC897BD792", + "refDict": "06EED564-BBA9-4747-8D73-AF809A330CB8", + "uiHint": "", + "id": "F458E86D-84D6-45A1-9DD3-51E6C8170D7F" + }, + { + "defKey": "MARITAL", + "defName": "婚姻状况", + "comment": "", + "type": "VARCHAR", + "len": "32", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "73FD2BAD-2358-4336-B96D-45DC897BD792", + "refDict": "EA1587B7-3954-437A-BFE0-FCB0453BCABA", + "uiHint": "", + "id": "7275E578-6893-4922-AC69-95B261BFBD61" + }, + { + "defKey": "DOMICILE_PLACE_PROVINCE", + "defName": "籍贯(省)", + "comment": "", + "type": "VARCHAR", + "len": "60", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "9092C4E0-1A54-4859-ABBB-5B62DBC27573", + "refDict": "", + "uiHint": "", + "id": "F04BF130-3EC1-4E02-9DED-3214CA88E352" + }, + { + "defKey": "DOMICILE_PLACE_CITY", + "defName": "籍贯(市)", + "comment": "", + "type": "VARCHAR", + "len": "32", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "73FD2BAD-2358-4336-B96D-45DC897BD792", + "refDict": "", + "uiHint": "", + "id": "B97F5BC2-33DE-4857-9DB8-ECFD02C9040C" + }, + { + "defKey": "DOMICILE_PLACE_ADDRESS", + "defName": "户籍地址", + "comment": "", + "type": "VARCHAR", + "len": "60", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "domain": "9092C4E0-1A54-4859-ABBB-5B62DBC27573", + "refDict": "", + "uiHint": "", + "id": "812ADF1D-8C03-40CA-B030-E539838FB889" + } + ], + "id": "F30202B9-4B5D-4CE7-87CE-B3890C84D3F2" + } + ], + "dbConn": [] +} diff --git a/salpa-admin/src/main/resources/i18n/messages.properties b/salpa-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..71cf52d --- /dev/null +++ b/salpa-admin/src/main/resources/i18n/messages.properties @@ -0,0 +1,37 @@ +#错误消息 +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}次,帐户锁定10分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +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=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{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/salpa-admin/src/main/resources/logback.xml b/salpa-admin/src/main/resources/logback.xml new file mode 100644 index 0000000..38e4b96 --- /dev/null +++ b/salpa-admin/src/main/resources/logback.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salpa-admin/src/main/resources/mybatis/mybatis-config.xml b/salpa-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..ac47c03 --- /dev/null +++ b/salpa-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/salpa-admin/src/main/resources/word.docx b/salpa-admin/src/main/resources/word.docx new file mode 100644 index 0000000..28f285e --- /dev/null +++ b/salpa-admin/src/main/resources/word.docx @@ -0,0 +1,29 @@ +{{databaseName}} + +overview + {{tables}}表名 + 类型 + 备注 +[tableName] +[tableType] +[tableComment] + + {{?tableColumns}} +{{tableName}} + + {{columns}}名称 + 类型 + 主键 + 可空 + 自增 + 默认值 + 注释 +[name] +[type] +[primaryKey] +[nullable] +[autoincrement] +[defaultValue] +[comment] + + {{/tableColumns}} diff --git a/salpa-common/pom.xml b/salpa-common/pom.xml new file mode 100644 index 0000000..33e2eab --- /dev/null +++ b/salpa-common/pom.xml @@ -0,0 +1,166 @@ + + + + salpa + com.salpa + 3.8.3 + + 4.0.0 + + salpa-common + + + common通用工具 + + + + + + + org.springframework + spring-context-support + + + + + org.springframework + spring-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.alibaba.fastjson2 + fastjson2 + + + + + commons-io + commons-io + + + + + commons-fileupload + commons-fileupload + + + + org.apache.commons + commons-compress + + + + + org.apache.poi + poi-ooxml + + + + + com.deepoove + poi-tl + + + + + org.yaml + snakeyaml + + + + + io.jsonwebtoken + jjwt + + + + + javax.xml.bind + jaxb-api + + + + org.projectlombok + lombok + 1.18.24 + + + + org.mapstruct + mapstruct + 1.4.2.Final + + + + + net.sourceforge.plantuml + plantuml + 1.2022.6 + + + + org.freemarker + freemarker + 2.3.31 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + eu.bitwalker + UserAgentUtils + + + + + javax.servlet + javax.servlet-api + + + + + \ No newline at end of file diff --git a/salpa-common/salpa-common.iml b/salpa-common/salpa-common.iml new file mode 100644 index 0000000..b9d2e44 --- /dev/null +++ b/salpa-common/salpa-common.iml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salpa-common/src/main/java/com/salpa/common/annotation/Anonymous.java b/salpa-common/src/main/java/com/salpa/common/annotation/Anonymous.java new file mode 100644 index 0000000..ad79236 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/annotation/Anonymous.java @@ -0,0 +1,19 @@ +package com.salpa.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 salpa + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Anonymous +{ +} diff --git a/salpa-common/src/main/java/com/salpa/common/annotation/DataScope.java b/salpa-common/src/main/java/com/salpa/common/annotation/DataScope.java new file mode 100644 index 0000000..1781315 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/annotation/DataScope.java @@ -0,0 +1,28 @@ +package com.salpa.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 salpa + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope +{ + /** + * 部门表的别名 + */ + public String deptAlias() default ""; + + /** + * 用户表的别名 + */ + public String userAlias() default ""; +} diff --git a/salpa-common/src/main/java/com/salpa/common/annotation/DataSource.java b/salpa-common/src/main/java/com/salpa/common/annotation/DataSource.java new file mode 100644 index 0000000..d075996 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/annotation/DataSource.java @@ -0,0 +1,28 @@ +package com.salpa.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.salpa.common.enums.DataSourceType; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author salpa + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource +{ + /** + * 切换数据源名称 + */ + public DataSourceType value() default DataSourceType.MASTER; +} diff --git a/salpa-common/src/main/java/com/salpa/common/annotation/Excel.java b/salpa-common/src/main/java/com/salpa/common/annotation/Excel.java new file mode 100644 index 0000000..1487233 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/annotation/Excel.java @@ -0,0 +1,183 @@ +package com.salpa.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.salpa.common.utils.poi.ExcelHandlerAdapter; + +/** + * 自定义导出Excel数据注解 + * + * @author salpa + */ +@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 {}; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出类型(0数字 1字符串) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + public enum Align + { + AUTO(0), LEFT(1), CENTER(2), RIGHT(3); + private final int value; + + Align(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } + + /** + * 字段类型(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); + 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/salpa-common/src/main/java/com/salpa/common/annotation/Excels.java b/salpa-common/src/main/java/com/salpa/common/annotation/Excels.java new file mode 100644 index 0000000..caea732 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.salpa.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解集 + * + * @author salpa + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Excels +{ + public Excel[] value(); +} diff --git a/salpa-common/src/main/java/com/salpa/common/annotation/Log.java b/salpa-common/src/main/java/com/salpa/common/annotation/Log.java new file mode 100644 index 0000000..9e5c24e --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/annotation/Log.java @@ -0,0 +1,46 @@ +package com.salpa.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.salpa.common.enums.BusinessType; +import com.salpa.common.enums.OperatorType; + +/** + * 自定义操作日志记录注解 + * + * @author salpa + * + */ +@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; +} diff --git a/salpa-common/src/main/java/com/salpa/common/annotation/RateLimiter.java b/salpa-common/src/main/java/com/salpa/common/annotation/RateLimiter.java new file mode 100644 index 0000000..c87ef2c --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/annotation/RateLimiter.java @@ -0,0 +1,40 @@ +package com.salpa.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.salpa.common.constant.CacheConstants; +import com.salpa.common.enums.LimitType; + +/** + * 限流注解 + * + * @author salpa + */ +@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/salpa-common/src/main/java/com/salpa/common/annotation/RepeatSubmit.java b/salpa-common/src/main/java/com/salpa/common/annotation/RepeatSubmit.java new file mode 100644 index 0000000..f1106e8 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.salpa.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 salpa + * + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit +{ + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + public int interval() default 5000; + + /** + * 提示消息 + */ + public String message() default "不允许重复提交,请稍候再试"; +} diff --git a/salpa-common/src/main/java/com/salpa/common/config/SalpaConfig.java b/salpa-common/src/main/java/com/salpa/common/config/SalpaConfig.java new file mode 100644 index 0000000..a736b41 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/config/SalpaConfig.java @@ -0,0 +1,143 @@ +package com.salpa.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author salpa + */ +@Component +@ConfigurationProperties(prefix = "metaops") +public class SalpaConfig +{ + /** 项目名称 */ + private String name; + + /** 版本 */ + private String version; + + /** 版权年份 */ + private String copyrightYear; + + /** 实例演示开关 */ + private boolean demoEnabled; + + /** 上传路径 */ + 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 boolean isDemoEnabled() + { + return demoEnabled; + } + + public void setDemoEnabled(boolean demoEnabled) + { + this.demoEnabled = demoEnabled; + } + + public static String getProfile() + { + return profile; + } + + public void setProfile(String profile) + { + SalpaConfig.profile = profile; + } + + public static boolean isAddressEnabled() + { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) + { + SalpaConfig.addressEnabled = addressEnabled; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + SalpaConfig.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"; + } + + /** + * 获取drivers路径 + */ + public static String getDriverPath() + { + return getProfile() + "/driver"; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/constant/CacheConstants.java b/salpa-common/src/main/java/com/salpa/common/constant/CacheConstants.java new file mode 100644 index 0000000..5a2aa27 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/constant/CacheConstants.java @@ -0,0 +1,39 @@ +package com.salpa.common.constant; + +/** + * 缓存的key 常量 + * + * @author salpa + */ +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:"; +} diff --git a/salpa-common/src/main/java/com/salpa/common/constant/Constants.java b/salpa-common/src/main/java/com/salpa/common/constant/Constants.java new file mode 100644 index 0000000..978a7c1 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/constant/Constants.java @@ -0,0 +1,137 @@ +package com.salpa.common.constant; + +import io.jsonwebtoken.Claims; + +/** + * 通用常量信息 + * + * @author salpa + */ +public class Constants +{ + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * 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 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:"; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.salpa" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.salpa.common.utils.file" }; +} diff --git a/salpa-common/src/main/java/com/salpa/common/constant/GenConstants.java b/salpa-common/src/main/java/com/salpa/common/constant/GenConstants.java new file mode 100644 index 0000000..8ed7ce3 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/constant/GenConstants.java @@ -0,0 +1,117 @@ +package com.salpa.common.constant; + +/** + * 代码生成通用常量 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/constant/HttpStatus.java b/salpa-common/src/main/java/com/salpa/common/constant/HttpStatus.java new file mode 100644 index 0000000..31e015b --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/constant/HttpStatus.java @@ -0,0 +1,89 @@ +package com.salpa.common.constant; + +/** + * 返回状态码 + * + * @author salpa + */ +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; +} diff --git a/salpa-common/src/main/java/com/salpa/common/constant/ScheduleConstants.java b/salpa-common/src/main/java/com/salpa/common/constant/ScheduleConstants.java new file mode 100644 index 0000000..eff2f07 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.salpa.common.constant; + +/** + * 任务调度通用常量 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/constant/UserConstants.java b/salpa-common/src/main/java/com/salpa/common/constant/UserConstants.java new file mode 100644 index 0000000..6b5813f --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/constant/UserConstants.java @@ -0,0 +1,78 @@ +package com.salpa.common.constant; + +/** + * 用户常量信息 + * + * @author salpa + */ +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_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 String UNIQUE = "0"; + public final static String NOT_UNIQUE = "1"; + + /** + * 用户名长度限制 + */ + 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/salpa-common/src/main/java/com/salpa/common/core/controller/BaseController.java b/salpa-common/src/main/java/com/salpa/common/core/controller/BaseController.java new file mode 100644 index 0000000..91933db --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/controller/BaseController.java @@ -0,0 +1,186 @@ +package com.salpa.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.salpa.common.constant.HttpStatus; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.core.page.PageDomain; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.core.page.TableSupport; +import com.salpa.common.utils.DateUtils; +import com.salpa.common.utils.PageUtils; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.sql.SqlUtil; + +/** + * web层通用数据处理 + * + * @author salpa + */ +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 error(String message) + { + return AjaxResult.error(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/salpa-common/src/main/java/com/salpa/common/core/domain/AjaxResult.java b/salpa-common/src/main/java/com/salpa/common/core/domain/AjaxResult.java new file mode 100644 index 0000000..9ef0be4 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/AjaxResult.java @@ -0,0 +1,162 @@ +package com.salpa.common.core.domain; + +import java.util.HashMap; +import com.salpa.common.constant.HttpStatus; +import com.salpa.common.utils.StringUtils; + +/** + * 操作消息提醒 + * + * @author salpa + */ +public class AjaxResult extends HashMap +{ + 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); + } + + /** + * 返回错误消息 + * + * @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); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/core/domain/BaseEntity.java b/salpa-common/src/main/java/com/salpa/common/core/domain/BaseEntity.java new file mode 100644 index 0000000..3bf6bfc --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/BaseEntity.java @@ -0,0 +1,114 @@ +package com.salpa.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; + +/** + * Entity基类 + * + * @author salpa + */ +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 搜索值 */ + 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; + + /** 请求参数 */ + private Map 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 getParams() + { + if (params == null) + { + params = new HashMap<>(); + } + return params; + } + + public void setParams(Map params) + { + this.params = params; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/core/domain/JsonData.java b/salpa-common/src/main/java/com/salpa/common/core/domain/JsonData.java new file mode 100644 index 0000000..73ce0d1 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/JsonData.java @@ -0,0 +1,48 @@ +package com.salpa.common.core.domain; + + +import lombok.Data; + +import java.util.Optional; + +@Data +public class JsonData { + + /** + * maybe null + */ + private T data; + + /** + * only exists when error happened + */ + private String errCode; + + /** + * only exists when error happened + */ + private String errMessage; + + public static JsonData ok() { + return ok(Optional.empty()); + } + + public static JsonData ok(T data) { + JsonData jsonData = new JsonData<>(); + jsonData.setData(data); + return jsonData; + } + + public static JsonData ok(Optional data) { + JsonData jsonData = new JsonData<>(); + jsonData.setData(data.orElse(null)); + return jsonData; + } + + public static JsonData error(String errorCode, String errMessage) { + JsonData jsonData = new JsonData<>(); + jsonData.setErrCode(errorCode); + jsonData.setErrMessage(errMessage); + return jsonData; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/core/domain/R.java b/salpa-common/src/main/java/com/salpa/common/core/domain/R.java new file mode 100644 index 0000000..4ee7aac --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/R.java @@ -0,0 +1,105 @@ +package com.salpa.common.core.domain; + +import java.io.Serializable; +import com.salpa.common.constant.HttpStatus; + +/** + * 响应信息主体 + * + * @author salpa + */ +public class R 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 R ok() + { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) + { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static R fail() + { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) + { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static R restResult(T data, int code, String msg) + { + R 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; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/core/domain/TreeEntity.java b/salpa-common/src/main/java/com/salpa/common/core/domain/TreeEntity.java new file mode 100644 index 0000000..842e74b --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.salpa.common.core.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tree基类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/core/domain/TreeSelect.java b/salpa-common/src/main/java/com/salpa/common/core/domain/TreeSelect.java new file mode 100644 index 0000000..8e13c86 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/TreeSelect.java @@ -0,0 +1,77 @@ +package com.salpa.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.salpa.common.core.domain.entity.SysDept; +import com.salpa.common.core.domain.entity.SysMenu; + +/** + * Treeselect树结构实体类 + * + * @author salpa + */ +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 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 getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDept.java b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDept.java new file mode 100644 index 0000000..b195a8a --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDept.java @@ -0,0 +1,203 @@ +package com.salpa.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 部门表 sys_dept + * + * @author salpa + */ +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 children = new ArrayList(); + + 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 getChildren() + { + return children; + } + + public void setChildren(List 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/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDictData.java b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDictData.java new file mode 100644 index 0000000..eac5970 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDictData.java @@ -0,0 +1,176 @@ +package com.salpa.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 字典数据表 sys_dict_data + * + * @author salpa + */ +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) ? true : false; + } + + 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/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDictType.java b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDictType.java new file mode 100644 index 0000000..a1451b2 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysDictType.java @@ -0,0 +1,96 @@ +package com.salpa.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 字典类型表 sys_dict_type + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysMenu.java b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysMenu.java new file mode 100644 index 0000000..86aa9d3 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysMenu.java @@ -0,0 +1,259 @@ +package com.salpa.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 菜单权限表 sys_menu + * + * @author salpa + */ +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; + + /** 是否为外链(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 children = new ArrayList(); + + 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 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 getChildren() + { + return children; + } + + public void setChildren(List 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("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/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysRole.java b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysRole.java new file mode 100644 index 0000000..6fe5da8 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysRole.java @@ -0,0 +1,226 @@ +package com.salpa.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 角色表 sys_role + * + * @author salpa + */ +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 String 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; + + 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; + } + + @NotBlank(message = "显示顺序不能为空") + public String getRoleSort() + { + return roleSort; + } + + public void setRoleSort(String 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; + } + + @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/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysUser.java b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysUser.java new file mode 100644 index 0000000..872ce76 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/entity/SysUser.java @@ -0,0 +1,324 @@ +package com.salpa.common.core.domain.entity; + +import java.util.Date; +import java.util.List; +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.annotation.Excel.Type; +import com.salpa.common.annotation.Excels; +import com.salpa.common.core.domain.BaseEntity; +import com.salpa.common.xss.Xss; + +/** + * 用户对象 sys_user + * + * @author salpa + */ +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @Excel(name = "用户序号", 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 = "手机号码") + 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 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 getRoles() + { + return roles; + } + + public void setRoles(List 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/salpa-common/src/main/java/com/salpa/common/core/domain/model/LoginBody.java b/salpa-common/src/main/java/com/salpa/common/core/domain/model/LoginBody.java new file mode 100644 index 0000000..f3578cf --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/model/LoginBody.java @@ -0,0 +1,69 @@ +package com.salpa.common.core.domain.model; + +/** + * 用户登录对象 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/core/domain/model/LoginUser.java b/salpa-common/src/main/java/com/salpa/common/core/domain/model/LoginUser.java new file mode 100644 index 0000000..b16d9b5 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/model/LoginUser.java @@ -0,0 +1,266 @@ +package com.salpa.common.core.domain.model; + +import java.util.Collection; +import java.util.Set; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import com.alibaba.fastjson2.annotation.JSONField; +import com.salpa.common.core.domain.entity.SysUser; + +/** + * 登录用户身份权限 + * + * @author salpa + */ +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 permissions; + + /** + * 用户信息 + */ + private SysUser user; + + 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; + } + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + + @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 getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + public Collection getAuthorities() + { + return null; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/core/domain/model/RegisterBody.java b/salpa-common/src/main/java/com/salpa/common/core/domain/model/RegisterBody.java new file mode 100644 index 0000000..8cb297b --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/domain/model/RegisterBody.java @@ -0,0 +1,11 @@ +package com.salpa.common.core.domain.model; + +/** + * 用户注册对象 + * + * @author salpa + */ +public class RegisterBody extends LoginBody +{ + +} diff --git a/salpa-common/src/main/java/com/salpa/common/core/page/PageDomain.java b/salpa-common/src/main/java/com/salpa/common/core/page/PageDomain.java new file mode 100644 index 0000000..63cf054 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/page/PageDomain.java @@ -0,0 +1,101 @@ +package com.salpa.common.core.page; + +import com.salpa.common.utils.StringUtils; + +/** + * 分页数据 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/core/page/TableDataInfo.java b/salpa-common/src/main/java/com/salpa/common/core/page/TableDataInfo.java new file mode 100644 index 0000000..b949942 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/page/TableDataInfo.java @@ -0,0 +1,85 @@ +package com.salpa.common.core.page; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/core/page/TableSupport.java b/salpa-common/src/main/java/com/salpa/common/core/page/TableSupport.java new file mode 100644 index 0000000..e0b0eb0 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/page/TableSupport.java @@ -0,0 +1,56 @@ +package com.salpa.common.core.page; + +import com.salpa.common.core.text.Convert; +import com.salpa.common.utils.ServletUtils; + +/** + * 表格数据处理 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/core/redis/RedisCache.java b/salpa-common/src/main/java/com/salpa/common/core/redis/RedisCache.java new file mode 100644 index 0000000..4ad9d77 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/redis/RedisCache.java @@ -0,0 +1,246 @@ +package com.salpa.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 salpa + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisCache +{ + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public 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 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) + { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) + { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public long deleteObject(final Collection collection) + { + return redisTemplate.delete(collection); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) + { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public 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 getCacheMapValue(final String key, final String hKey) + { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 删除Hash中的数据 + * + * @param key + * @param hKey + */ + public void delCacheMapValue(final String key, final String hKey) + { + HashOperations hashOperations = redisTemplate.opsForHash(); + hashOperations.delete(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) + { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) + { + return redisTemplate.keys(pattern); + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/core/text/CharsetKit.java b/salpa-common/src/main/java/com/salpa/common/core/text/CharsetKit.java new file mode 100644 index 0000000..70cadf5 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +package com.salpa.common.core.text; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.salpa.common.utils.StringUtils; + +/** + * 字符集工具类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/core/text/Convert.java b/salpa-common/src/main/java/com/salpa/common/core/text/Convert.java new file mode 100644 index 0000000..a25bf77 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/text/Convert.java @@ -0,0 +1,1000 @@ +package com.salpa.common.core.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; +import com.salpa.common.utils.StringUtils; +import org.apache.commons.lang3.ArrayUtils; + +/** + * 类型转换器 + * + * @author salpa + */ +public class Convert +{ + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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(); + } + + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @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数组
+ * + * @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数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static > E toEnum(Class 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对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value) + { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @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 new BigDecimal((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
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
+ * 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)); + } + + /** + * 将对象转为字符串
+ * 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 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 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++) + { + s += (digit[(int) (Math.floor(n * 10 * 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/salpa-common/src/main/java/com/salpa/common/core/text/StrFormatter.java b/salpa-common/src/main/java/com/salpa/common/core/text/StrFormatter.java new file mode 100644 index 0000000..61b0f1f --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.salpa.common.core.text; + +import com.salpa.common.utils.StringUtils; + +/** + * 字符串格式化 + * + * @author salpa + */ +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 = '}'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @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/salpa-common/src/main/java/com/salpa/common/enums/BusinessStatus.java b/salpa-common/src/main/java/com/salpa/common/enums/BusinessStatus.java new file mode 100644 index 0000000..20b234c --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.salpa.common.enums; + +/** + * 操作状态 + * + * @author salpa + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/salpa-common/src/main/java/com/salpa/common/enums/BusinessType.java b/salpa-common/src/main/java/com/salpa/common/enums/BusinessType.java new file mode 100644 index 0000000..8121dcc --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.salpa.common.enums; + +/** + * 业务操作类型 + * + * @author salpa + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/salpa-common/src/main/java/com/salpa/common/enums/DataSourceType.java b/salpa-common/src/main/java/com/salpa/common/enums/DataSourceType.java new file mode 100644 index 0000000..b8f17f0 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/enums/DataSourceType.java @@ -0,0 +1,19 @@ +package com.salpa.common.enums; + +/** + * 数据源 + * + * @author salpa + */ +public enum DataSourceType +{ + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE +} diff --git a/salpa-common/src/main/java/com/salpa/common/enums/HttpMethod.java b/salpa-common/src/main/java/com/salpa/common/enums/HttpMethod.java new file mode 100644 index 0000000..f1dfff7 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/enums/HttpMethod.java @@ -0,0 +1,36 @@ +package com.salpa.common.enums; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.lang.Nullable; + +/** + * 请求方式 + * + * @author salpa + */ +public enum HttpMethod +{ + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + private static final Map 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/salpa-common/src/main/java/com/salpa/common/enums/LimitType.java b/salpa-common/src/main/java/com/salpa/common/enums/LimitType.java new file mode 100644 index 0000000..e30c506 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.salpa.common.enums; + +/** + * 限流类型 + * + * @author salpa + */ + +public enum LimitType +{ + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/salpa-common/src/main/java/com/salpa/common/enums/OperatorType.java b/salpa-common/src/main/java/com/salpa/common/enums/OperatorType.java new file mode 100644 index 0000000..1543721 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.salpa.common.enums; + +/** + * 操作人类别 + * + * @author salpa + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/salpa-common/src/main/java/com/salpa/common/enums/UserStatus.java b/salpa-common/src/main/java/com/salpa/common/enums/UserStatus.java new file mode 100644 index 0000000..366893f --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/enums/UserStatus.java @@ -0,0 +1,30 @@ +package com.salpa.common.enums; + +/** + * 用户状态 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/DemoModeException.java b/salpa-common/src/main/java/com/salpa/common/exception/DemoModeException.java new file mode 100644 index 0000000..f61f855 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.salpa.common.exception; + +/** + * 演示模式异常 + * + * @author salpa + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/exception/GlobalException.java b/salpa-common/src/main/java/com/salpa/common/exception/GlobalException.java new file mode 100644 index 0000000..7a62ce9 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.salpa.common.exception; + +/** + * 全局异常 + * + * @author salpa + */ +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; + } + + public String getMessage() + { + return message; + } + + public GlobalException setMessage(String message) + { + this.message = message; + return this; + } +} \ No newline at end of file diff --git a/salpa-common/src/main/java/com/salpa/common/exception/ServiceException.java b/salpa-common/src/main/java/com/salpa/common/exception/ServiceException.java new file mode 100644 index 0000000..dfd366a --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/ServiceException.java @@ -0,0 +1,73 @@ +package com.salpa.common.exception; + +/** + * 业务异常 + * + * @author salpa + */ +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; + } + + 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/salpa-common/src/main/java/com/salpa/common/exception/SystemException.java b/salpa-common/src/main/java/com/salpa/common/exception/SystemException.java new file mode 100644 index 0000000..992d2b7 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/SystemException.java @@ -0,0 +1,27 @@ +package com.salpa.common.exception; + +public class SystemException extends RuntimeException{ + + private static final String MSG_INTERNAL_SERVER_ERROR = "服务器开小差了,请稍后再试"; + + public static final SystemException INTERNAL_SERVER_ERROR = new SystemException(MSG_INTERNAL_SERVER_ERROR); + + /** + * @param msg the detail message + */ + public SystemException(String msg) { + super(msg); + } + + /** + * @param msg the detail message + * @param cause the nested exception + */ + public SystemException(String msg, Throwable cause) { + super(msg, cause); + } + + public static SystemException internalServerErrorWithCause(Throwable cause) { + return new SystemException(MSG_INTERNAL_SERVER_ERROR, cause); + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/exception/UtilException.java b/salpa-common/src/main/java/com/salpa/common/exception/UtilException.java new file mode 100644 index 0000000..f487e11 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.salpa.common.exception; + +/** + * 工具类异常 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/base/BaseException.java b/salpa-common/src/main/java/com/salpa/common/exception/base/BaseException.java new file mode 100644 index 0000000..5a034bf --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.salpa.common.exception.base; + +import com.salpa.common.utils.MessageUtils; +import com.salpa.common.utils.StringUtils; + +/** + * 基础异常 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/file/FileException.java b/salpa-common/src/main/java/com/salpa/common/exception/file/FileException.java new file mode 100644 index 0000000..5f1cb24 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.salpa.common.exception.file; + +import com.salpa.common.exception.base.BaseException; + +/** + * 文件信息异常类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/file/FileNameLengthLimitExceededException.java b/salpa-common/src/main/java/com/salpa/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..85a9e9c --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.salpa.common.exception.file; + +/** + * 文件名称超长限制异常类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/file/FileSizeLimitExceededException.java b/salpa-common/src/main/java/com/salpa/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..0ba1bb2 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.salpa.common.exception.file; + +/** + * 文件名大小限制异常类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/file/InvalidExtensionException.java b/salpa-common/src/main/java/com/salpa/common/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..70078eb --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,81 @@ +package com.salpa.common.exception.file; + +import java.util.Arrays; +import org.apache.commons.fileupload.FileUploadException; + +/** + * 文件上传 误异常类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/job/TaskException.java b/salpa-common/src/main/java/com/salpa/common/exception/job/TaskException.java new file mode 100644 index 0000000..00df7e0 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/job/TaskException.java @@ -0,0 +1,34 @@ +package com.salpa.common.exception.job; + +/** + * 计划策略异常 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/user/CaptchaException.java b/salpa-common/src/main/java/com/salpa/common/exception/user/CaptchaException.java new file mode 100644 index 0000000..f704dd2 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.salpa.common.exception.user; + +/** + * 验证码错误异常类 + * + * @author salpa + */ +public class CaptchaException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaException() + { + super("user.jcaptcha.error", null); + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/exception/user/CaptchaExpireException.java b/salpa-common/src/main/java/com/salpa/common/exception/user/CaptchaExpireException.java new file mode 100644 index 0000000..28776d9 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.salpa.common.exception.user; + +/** + * 验证码失效异常类 + * + * @author salpa + */ +public class CaptchaExpireException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", null); + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/exception/user/UserException.java b/salpa-common/src/main/java/com/salpa/common/exception/user/UserException.java new file mode 100644 index 0000000..6023961 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.salpa.common.exception.user; + +import com.salpa.common.exception.base.BaseException; + +/** + * 用户信息异常类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/exception/user/UserPasswordNotMatchException.java b/salpa-common/src/main/java/com/salpa/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 0000000..be0badd --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.salpa.common.exception.user; + +/** + * 用户密码不正确或不符合规范异常类 + * + * @author salpa + */ +public class UserPasswordNotMatchException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordNotMatchException() + { + super("user.password.not.match", null); + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/filter/RepeatableFilter.java b/salpa-common/src/main/java/com/salpa/common/filter/RepeatableFilter.java new file mode 100644 index 0000000..85810e7 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.salpa.common.filter; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; +import com.salpa.common.utils.StringUtils; + +/** + * Repeatable 过滤器 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/filter/RepeatedlyRequestWrapper.java b/salpa-common/src/main/java/com/salpa/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..8fc1bf8 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,75 @@ +package com.salpa.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import com.salpa.common.utils.http.HttpHelper; + +/** + * 构建可重复读取inputStream的request + * + * @author salpa + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException + { + super(request); + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + + body = HttpHelper.getBodyString(request).getBytes("UTF-8"); + } + + @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/salpa-common/src/main/java/com/salpa/common/filter/XssFilter.java b/salpa-common/src/main/java/com/salpa/common/filter/XssFilter.java new file mode 100644 index 0000000..9bfbf15 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/filter/XssFilter.java @@ -0,0 +1,74 @@ +package com.salpa.common.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.salpa.common.utils.StringUtils; + +/** + * 防止XSS攻击的过滤器 + * + * @author salpa + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] url = tempExcludes.split(","); + for (int i = 0; url != null && i < url.length; i++) + { + excludes.add(url[i]); + } + } + } + + @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 || method.matches("GET") || method.matches("DELETE")) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/salpa-common/src/main/java/com/salpa/common/filter/XssHttpServletRequestWrapper.java b/salpa-common/src/main/java/com/salpa/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..9e73e94 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.salpa.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.html.EscapeUtil; + +/** + * XSS过滤处理 + * + * @author salpa + */ +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[] escapseValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapseValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapseValues; + } + 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/salpa-common/src/main/java/com/salpa/common/utils/Arith.java b/salpa-common/src/main/java/com/salpa/common/utils/Arith.java new file mode 100644 index 0000000..ddf2f3c --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/Arith.java @@ -0,0 +1,114 @@ +package com.salpa.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 精确的浮点数运算 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/DateUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/DateUtils.java new file mode 100644 index 0000000..6d5c2b1 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/DateUtils.java @@ -0,0 +1,187 @@ +package com.salpa.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 salpa + */ +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))); + } + + /** + * 计算两个时间差 + */ + public static String getDatePoor(Date endDate, Date nowDate) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - nowDate.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/salpa-common/src/main/java/com/salpa/common/utils/DictUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/DictUtils.java new file mode 100644 index 0000000..513ff43 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/DictUtils.java @@ -0,0 +1,186 @@ +package com.salpa.common.utils; + +import java.util.Collection; +import java.util.List; +import com.alibaba.fastjson2.JSONArray; +import com.salpa.common.constant.CacheConstants; +import com.salpa.common.core.domain.entity.SysDictData; +import com.salpa.common.core.redis.RedisCache; +import com.salpa.common.utils.spring.SpringUtils; + +/** + * 字典工具类 + * + * @author salpa + */ +public class DictUtils +{ + /** + * 分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache(String key, List dictDatas) + { + SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + public static List 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) + { + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + 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 datas = getDictCache(dictType); + + if (StringUtils.isNotNull(datas)) + { + 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 datas = getDictCache(dictType); + + if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) + { + 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 key 字典键 + */ + public static void removeDictCache(String key) + { + SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key)); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() + { + Collection 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/salpa-common/src/main/java/com/salpa/common/utils/ExceptionUtil.java b/salpa-common/src/main/java/com/salpa/common/utils/ExceptionUtil.java new file mode 100644 index 0000000..fc9e24b --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/ExceptionUtil.java @@ -0,0 +1,39 @@ +package com.salpa.common.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * 错误信息处理类。 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/LogUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/LogUtils.java new file mode 100644 index 0000000..975bb7a --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.salpa.common.utils; + +/** + * 处理并记录日志文件 + * + * @author salpa + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/utils/MessageUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/MessageUtils.java new file mode 100644 index 0000000..aba652e --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/MessageUtils.java @@ -0,0 +1,26 @@ +package com.salpa.common.utils; + +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import com.salpa.common.utils.spring.SpringUtils; + +/** + * 获取i18n资源文件 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/PageUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/PageUtils.java new file mode 100644 index 0000000..e2a4182 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/PageUtils.java @@ -0,0 +1,35 @@ +package com.salpa.common.utils; + +import com.github.pagehelper.PageHelper; +import com.salpa.common.core.page.PageDomain; +import com.salpa.common.core.page.TableSupport; +import com.salpa.common.utils.sql.SqlUtil; + +/** + * 分页工具类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/SecurityUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/SecurityUtils.java new file mode 100644 index 0000000..aa5f705 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/SecurityUtils.java @@ -0,0 +1,120 @@ +package com.salpa.common.utils; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import com.salpa.common.constant.HttpStatus; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.exception.ServiceException; + +/** + * 安全服务工具类 + * + * @author salpa + */ +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; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/utils/ServletUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/ServletUtils.java new file mode 100644 index 0000000..cd8179b --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/ServletUtils.java @@ -0,0 +1,186 @@ +package com.salpa.common.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.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.salpa.common.constant.Constants; +import com.salpa.common.core.text.Convert; + +/** + * 客户端工具类 + * + * @author salpa + */ +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); + } + + /** + * 获取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/salpa-common/src/main/java/com/salpa/common/utils/StringUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/StringUtils.java new file mode 100644 index 0000000..25c7e43 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/StringUtils.java @@ -0,0 +1,583 @@ +package com.salpa.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.salpa.common.constant.Constants; +import com.salpa.common.core.text.StrFormatter; + +/** + * 字符串工具类 + * + * @author salpa + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils +{ + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static 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 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); + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @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 str2Set(String str, String sep) + { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) + { + List list = new ArrayList(); + 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; + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @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; + } + 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 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 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/salpa-common/src/main/java/com/salpa/common/utils/Threads.java b/salpa-common/src/main/java/com/salpa/common/utils/Threads.java new file mode 100644 index 0000000..cb4eba2 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/Threads.java @@ -0,0 +1,99 @@ +package com.salpa.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 salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/VerifyCodeUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/VerifyCodeUtils.java new file mode 100644 index 0000000..b2b7363 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/VerifyCodeUtils.java @@ -0,0 +1,228 @@ +package com.salpa.common.utils; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Random; +import javax.imageio.ImageIO; + +/** + * 验证码工具类 + * + * @author salpa + */ +public class VerifyCodeUtils +{ + // 使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符 + public static final String VERIFY_CODES = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ"; + + private static Random random = new SecureRandom(); + + /** + * 使用系统默认字符源生成验证码 + * + * @param verifySize 验证码长度 + * @return + */ + public static String generateVerifyCode(int verifySize) + { + return generateVerifyCode(verifySize, VERIFY_CODES); + } + + /** + * 使用指定源生成验证码 + * + * @param verifySize 验证码长度 + * @param sources 验证码字符源 + * @return + */ + public static String generateVerifyCode(int verifySize, String sources) + { + if (sources == null || sources.length() == 0) + { + sources = VERIFY_CODES; + } + int codesLen = sources.length(); + Random rand = new Random(System.currentTimeMillis()); + StringBuilder verifyCode = new StringBuilder(verifySize); + for (int i = 0; i < verifySize; i++) + { + verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1))); + } + return verifyCode.toString(); + } + + /** + * 输出指定验证码图片流 + * + * @param w + * @param h + * @param os + * @param code + * @throws IOException + */ + public static void outputImage(int w, int h, OutputStream os, String code) throws IOException + { + int verifySize = code.length(); + BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + Random rand = new Random(); + Graphics2D g2 = image.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + Color[] colors = new Color[5]; + Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN, Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, + Color.ORANGE, Color.PINK, Color.YELLOW }; + float[] fractions = new float[colors.length]; + for (int i = 0; i < colors.length; i++) + { + colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)]; + fractions[i] = rand.nextFloat(); + } + Arrays.sort(fractions); + + g2.setColor(Color.GRAY);// 设置边框色 + g2.fillRect(0, 0, w, h); + + Color c = getRandColor(200, 250); + g2.setColor(c);// 设置背景色 + g2.fillRect(0, 2, w, h - 4); + + // 绘制干扰线 + Random random = new Random(); + g2.setColor(getRandColor(160, 200));// 设置线条的颜色 + for (int i = 0; i < 20; i++) + { + int x = random.nextInt(w - 1); + int y = random.nextInt(h - 1); + int xl = random.nextInt(6) + 1; + int yl = random.nextInt(12) + 1; + g2.drawLine(x, y, x + xl + 40, y + yl + 20); + } + + // 添加噪点 + float yawpRate = 0.05f;// 噪声率 + int area = (int) (yawpRate * w * h); + for (int i = 0; i < area; i++) + { + int x = random.nextInt(w); + int y = random.nextInt(h); + int rgb = getRandomIntColor(); + image.setRGB(x, y, rgb); + } + + shear(g2, w, h, c);// 使图片扭曲 + + g2.setColor(getRandColor(100, 160)); + int fontSize = h - 4; + Font font = new Font("Algerian", Font.ITALIC, fontSize); + g2.setFont(font); + char[] chars = code.toCharArray(); + for (int i = 0; i < verifySize; i++) + { + AffineTransform affine = new AffineTransform(); + affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), + (w / verifySize) * i + fontSize / 2, h / 2); + g2.setTransform(affine); + g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10); + } + + g2.dispose(); + ImageIO.write(image, "jpg", os); + } + + private static Color getRandColor(int fc, int bc) + { + if (fc > 255) { + fc = 255; + } + if (bc > 255) { + bc = 255; + } + int r = fc + random.nextInt(bc - fc); + int g = fc + random.nextInt(bc - fc); + int b = fc + random.nextInt(bc - fc); + return new Color(r, g, b); + } + + private static int getRandomIntColor() + { + int[] rgb = getRandomRgb(); + int color = 0; + for (int c : rgb) + { + color = color << 8; + color = color | c; + } + return color; + } + + private static int[] getRandomRgb() + { + int[] rgb = new int[3]; + for (int i = 0; i < 3; i++) + { + rgb[i] = random.nextInt(255); + } + return rgb; + } + + private static void shear(Graphics g, int w1, int h1, Color color) + { + shearX(g, w1, h1, color); + shearY(g, w1, h1, color); + } + + private static void shearX(Graphics g, int w1, int h1, Color color) + { + + int period = random.nextInt(2); + + boolean borderGap = true; + int frames = 1; + int phase = random.nextInt(2); + + for (int i = 0; i < h1; i++) + { + double d = (double) (period >> 1) + * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); + g.copyArea(0, i, w1, 1, (int) d, 0); + if (borderGap) + { + g.setColor(color); + g.drawLine((int) d, i, 0, i); + g.drawLine((int) d + w1, i, w1, i); + } + } + + } + + private static void shearY(Graphics g, int w1, int h1, Color color) + { + + int period = random.nextInt(40) + 10; // 50; + + boolean borderGap = true; + int frames = 20; + int phase = 7; + for (int i = 0; i < w1; i++) + { + double d = (double) (period >> 1) + * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); + g.copyArea(i, 0, 1, h1, 0, (int) d); + if (borderGap) + { + g.setColor(color); + g.drawLine(i, (int) d, i, 0); + g.drawLine(i, (int) d + h1, i, h1); + } + + } + } +} \ No newline at end of file diff --git a/salpa-common/src/main/java/com/salpa/common/utils/bean/BeanUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/bean/BeanUtils.java new file mode 100644 index 0000000..ed27fe8 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.salpa.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 salpa + */ +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 getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + 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 getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + 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方法名中的属性名是否相等。
+ * 如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/salpa-common/src/main/java/com/salpa/common/utils/bean/BeanValidators.java b/salpa-common/src/main/java/com/salpa/common/utils/bean/BeanValidators.java new file mode 100644 index 0000000..85baca3 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.salpa.common.utils.bean; + +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; + +/** + * bean对象属性验证 + * + * @author salpa + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/utils/file/FileTypeUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..f3b3054 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/file/FileTypeUtils.java @@ -0,0 +1,76 @@ +package com.salpa.common.utils.file; + +import java.io.File; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件类型工具类 + * + * @author salpa + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

+ * 例如: salpa.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

+ * 例如: salpa.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/salpa-common/src/main/java/com/salpa/common/utils/file/FileUploadUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/file/FileUploadUtils.java new file mode 100644 index 0000000..9467eaa --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/file/FileUploadUtils.java @@ -0,0 +1,232 @@ +package com.salpa.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.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.Constants; +import com.salpa.common.exception.file.FileNameLengthLimitExceededException; +import com.salpa.common.exception.file.FileSizeLimitExceededException; +import com.salpa.common.exception.file.InvalidExtensionException; +import com.salpa.common.utils.DateUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.uuid.Seq; + +/** + * 文件上传工具类 + * + * @author salpa + */ +public class FileUploadUtils +{ + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 默认上传的地址 + */ + private static String defaultBaseDir = SalpaConfig.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 = file.getOriginalFilename(); + + 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 = SalpaConfig.getProfile().length() + 1; + String currentDir = StringUtils.substring(uploadDir, dirLastIndex); + return "/" + 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/salpa-common/src/main/java/com/salpa/common/utils/file/FileUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/file/FileUtils.java new file mode 100644 index 0000000..fb59352 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/file/FileUtils.java @@ -0,0 +1,293 @@ +package com.salpa.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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import com.salpa.common.config.SalpaConfig; +import com.salpa.common.utils.DateUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.uuid.IdUtils; +import org.apache.commons.io.FilenameUtils; + +/** + * 文件处理工具类 + * + * @author salpa + */ +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, SalpaConfig.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()) + { + file.delete(); + flag = true; + } + 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/salpa.png -- salpa.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/salpa.png -- salpa + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public static String getNameNotSuffix(String fileName) + { + if (fileName == null) + { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } + +} diff --git a/salpa-common/src/main/java/com/salpa/common/utils/file/ImageUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/file/ImageUtils.java new file mode 100644 index 0000000..3b36699 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/file/ImageUtils.java @@ -0,0 +1,98 @@ +package com.salpa.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.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.Constants; +import com.salpa.common.utils.StringUtils; + +/** + * 图片处理工具类 + * + * @author salpa + */ +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 = SalpaConfig.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/salpa-common/src/main/java/com/salpa/common/utils/file/MimeTypeUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..e38a3ea --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +package com.salpa.common.utils.file; + +/** + * 媒体类型工具类 + * + * @author salpa + */ +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", "jar" , + // 视频格式 + "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/salpa-common/src/main/java/com/salpa/common/utils/html/EscapeUtil.java b/salpa-common/src/main/java/com/salpa/common/utils/html/EscapeUtil.java new file mode 100644 index 0000000..f19c3eb --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +package com.salpa.common.utils.html; + +import com.salpa.common.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author salpa + */ +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['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 双引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".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 = ""; + String escape = EscapeUtil.escape(html); + // String html = "ipt>alert(\"XSS\")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/salpa-common/src/main/java/com/salpa/common/utils/html/HTMLFilter.java b/salpa-common/src/main/java/com/salpa/common/utils/html/HTMLFilter.java new file mode 100644 index 0000000..2ec7ff4 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +package com.salpa.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 salpa + */ +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 P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + 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. "" or "") + **/ + 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. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList 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 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 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>) 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, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", 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("")); + } + 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, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", 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(""); + } + } + 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[^>]*)?>")); + } + 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 ""; + } + } + } + } + + // 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 paramNames = new ArrayList<>(); + final List 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); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, 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 : "&" + 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/salpa-common/src/main/java/com/salpa/common/utils/http/HttpHelper.java b/salpa-common/src/main/java/com/salpa/common/utils/http/HttpHelper.java new file mode 100644 index 0000000..b5b3c02 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.salpa.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 javax.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http工具封装 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/http/HttpUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/http/HttpUtils.java new file mode 100644 index 0000000..94abb90 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/http/HttpUtils.java @@ -0,0 +1,274 @@ +package com.salpa.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.salpa.common.constant.Constants; +import com.salpa.common.utils.StringUtils; + +/** + * 通用http发送方法 + * + * @author salpa + */ +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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + 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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + 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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + 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/salpa-common/src/main/java/com/salpa/common/utils/ip/AddressUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/ip/AddressUtils.java new file mode 100644 index 0000000..21cace0 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/ip/AddressUtils.java @@ -0,0 +1,56 @@ +package com.salpa.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.Constants; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.http.HttpUtils; + +/** + * 获取地址类 + * + * @author salpa + */ +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 (SalpaConfig.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/salpa-common/src/main/java/com/salpa/common/utils/ip/IpUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/ip/IpUtils.java new file mode 100644 index 0000000..1c30434 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/ip/IpUtils.java @@ -0,0 +1,264 @@ +package com.salpa.common.utils.ip; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import javax.servlet.http.HttpServletRequest; +import com.salpa.common.utils.StringUtils; + +/** + * 获取IP方法 + * + * @author salpa + */ +public class IpUtils +{ + /** + * 获取客户端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 ip; + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) + { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } +} \ No newline at end of file diff --git a/salpa-common/src/main/java/com/salpa/common/utils/poi/ExcelHandlerAdapter.java b/salpa-common/src/main/java/com/salpa/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 0000000..fb0df16 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.salpa.common.utils.poi; + +/** + * Excel数据格式处理适配器 + * + * @author salpa + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args); +} diff --git a/salpa-common/src/main/java/com/salpa/common/utils/poi/ExcelUtil.java b/salpa-common/src/main/java/com/salpa/common/utils/poi/ExcelUtil.java new file mode 100644 index 0000000..ff4b0f1 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/poi/ExcelUtil.java @@ -0,0 +1,1432 @@ +package com.salpa.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.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.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 javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.RegExUtils; +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.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.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.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.annotation.Excel.Type; +import com.salpa.common.annotation.Excels; +import com.salpa.common.config.SalpaConfig; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.text.Convert; +import com.salpa.common.exception.UtilException; +import com.salpa.common.utils.DateUtils; +import com.salpa.common.utils.DictUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.file.FileTypeUtils; +import com.salpa.common.utils.file.FileUtils; +import com.salpa.common.utils.file.ImageUtils; +import com.salpa.common.utils.reflect.ReflectUtils; + +/** + * Excel相关处理 + * + * @author salpa + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * 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 styles; + + /** + * 导入导出数据列表 + */ + private List list; + + /** + * 注解列表 + */ + private List fields; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + + /** + * 最大高度 + */ + private short maxHeight; + + /** + * 统计列表 + */ + private Map statistics = new HashMap(); + + /** + * 数字格式 + */ + private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); + + /** + * 实体对象 + */ + public Class clazz; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + public void init(List list, String sheetName, String title, Type type) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + createExcelField(); + createWorkbook(); + createTitle(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + 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(), + this.fields.size() - 1)); + } + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(InputStream is) throws Exception + { + return importExcel(is, 0); + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) throws Exception + { + return importExcel(StringUtils.EMPTY, is, titleNum); + } + + /** + * 对excel表单指定表格索引名转换成list + * + * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception + { + this.type = Type.IMPORT; + this.wb = WorkbookFactory.create(is); + List list = new ArrayList(); + // 如果指定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 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 cellMap = new HashMap(); + // 获取表头 + 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 fields = this.getFields(); + Map fieldsMap = new HashMap(); + 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 entry : fieldsMap.entrySet()) + { + Object val = this.getCellValue(row, entry.getKey()); + + // 如果不存在实例则新建. + entity = (entity == null ? clazz.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(); + } + else if (StringUtils.isNotEmpty(attr.readConverterExp())) + { + val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); + } + else if (StringUtils.isNotEmpty(attr.dictType())) + { + val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr); + } + else if (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 list, String sheetName) + { + return exportExcel(list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult exportExcel(List 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 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 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) + { + Excel excel = (Excel) os[1]; + this.createCell(excel, row, column++); + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index, row); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + * @param row 单元格行 + */ + public void fillExcelData(int index, Row row) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + for (int i = startNo; i < endNo; i++) + { + row = sheet.createRow(i + 1 + rownum - startNo); + // 得到导出对象. + T vo = (T) list.get(i); + int column = 0; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + this.addCell(excel, row, vo, field, column++); + } + } + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) + { + // 写入各条记录,每条记录对应excel表中的一行 + Map styles = new HashMap(); + 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); + 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.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(IndexedColors.WHITE.getIndex()); + style.setFont(headerFont); + styles.put("header", 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(annotationStyles(wb)); + + return styles; + } + + /** + * 根据Excel注解创建表格样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = "data_" + excel.align() + "_" + excel.color(); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + 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()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + styles.put(key, style); + } + } + return styles; + } + + /** + * 创建单元格 + */ + public Cell createCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get("header")); + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType()) + { + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + 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) + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, attr.combo(), 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); + cell.setCellStyle(styles.get("data_" + attr.align() + "_" + attr.color())); + + // 用于读取对象中的属性 + 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)) + { + cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator)); + } + else if (value instanceof BigDecimal && -1 != attr.scale()) + { + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr)); + } + 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); + } + + /** + * 解析导出值 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(separator, propertyValue)) + { + 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(separator, propertyValue)) + { + 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) + { + try + { + Object instance = excel.handler().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class }); + value = formatMethod.invoke(instance, value, excel.args()); + } + catch (Exception e) + { + 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 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().toString() + "_" + filename + ".xlsx"; + return filename; + } + + /** + * 获取下载路径 + * + * @param filename 文件名称 + */ + public String getAbsoluteFile(String filename) + { + String downloadPath = SalpaConfig.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 getFields() + { + List fields = new ArrayList(); + List tempFields = new ArrayList<>(); + tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); + tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + for (Field field : tempFields) + { + // 单注解 + 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 (field.isAnnotationPresent(Excels.class)) + { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (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 getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + List 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 = String.valueOf(anchor.getRow1()) + "_" + String.valueOf(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 getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + for (POIXMLDocumentPart dr : sheet.getRelations()) + { + if (dr instanceof XSSFDrawing) + { + XSSFDrawing drawing = (XSSFDrawing) dr; + List 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; + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/utils/reflect/ReflectUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/reflect/ReflectUtils.java new file mode 100644 index 0000000..1ac8c41 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,410 @@ +package com.salpa.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.salpa.common.core.text.Convert; +import com.salpa.common.utils.DateUtils; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author salpa + */ +@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 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 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 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 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 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 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 Class 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/salpa-common/src/main/java/com/salpa/common/utils/sign/Base64.java b/salpa-common/src/main/java/com/salpa/common/utils/sign/Base64.java new file mode 100644 index 0000000..fb787fa --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.salpa.common.utils.sign; + +/** + * Base64工具类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/sign/Md5Utils.java b/salpa-common/src/main/java/com/salpa/common/utils/sign/Md5Utils.java new file mode 100644 index 0000000..7cd324b --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/sign/Md5Utils.java @@ -0,0 +1,67 @@ +package com.salpa.common.utils.sign; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Md5加密方法 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/spring/SpringUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/spring/SpringUtils.java new file mode 100644 index 0000000..4a63fbf --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/spring/SpringUtils.java @@ -0,0 +1,158 @@ +package com.salpa.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.salpa.common.utils.StringUtils; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author salpa + */ +@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 getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class 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 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/salpa-common/src/main/java/com/salpa/common/utils/sql/SqlUtil.java b/salpa-common/src/main/java/com/salpa/common/utils/sql/SqlUtil.java new file mode 100644 index 0000000..b22a771 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/sql/SqlUtil.java @@ -0,0 +1,61 @@ +package com.salpa.common.utils.sql; + +import com.salpa.common.exception.UtilException; +import com.salpa.common.utils.StringUtils; + +/** + * sql操作工具类 + * + * @author salpa + */ +public class SqlUtil +{ + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare "; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) + { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) + { + 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/salpa-common/src/main/java/com/salpa/common/utils/uuid/IdUtils.java b/salpa-common/src/main/java/com/salpa/common/utils/uuid/IdUtils.java new file mode 100644 index 0000000..b2ff007 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +package com.salpa.common.utils.uuid; + +/** + * ID生成器工具类 + * + * @author salpa + */ +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/salpa-common/src/main/java/com/salpa/common/utils/uuid/Seq.java b/salpa-common/src/main/java/com/salpa/common/utils/uuid/Seq.java new file mode 100644 index 0000000..041af32 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/uuid/Seq.java @@ -0,0 +1,86 @@ +package com.salpa.common.utils.uuid; + +import java.util.concurrent.atomic.AtomicInteger; +import com.salpa.common.utils.DateUtils; +import com.salpa.common.utils.StringUtils; + +/** + * @author salpa 序列生成类 + */ +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 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/salpa-common/src/main/java/com/salpa/common/utils/uuid/UUID.java b/salpa-common/src/main/java/com/salpa/common/utils/uuid/UUID.java new file mode 100644 index 0000000..e2e57d1 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +package com.salpa.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.salpa.common.exception.UtilException; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author salpa + */ +public final class UUID implements java.io.Serializable, Comparable +{ + 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 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 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} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @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 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@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 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@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 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @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; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@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 比较。 + * + *

+ * 如果两个 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); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/salpa-common/src/main/java/com/salpa/common/xss/Xss.java b/salpa-common/src/main/java/com/salpa/common/xss/Xss.java new file mode 100644 index 0000000..c227d00 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.salpa.common.xss; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author salpa + */ +@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[] payload() default {}; +} diff --git a/salpa-common/src/main/java/com/salpa/common/xss/XssValidator.java b/salpa-common/src/main/java/com/salpa/common/xss/XssValidator.java new file mode 100644 index 0000000..0ebe1a8 --- /dev/null +++ b/salpa-common/src/main/java/com/salpa/common/xss/XssValidator.java @@ -0,0 +1,34 @@ +package com.salpa.common.xss; + +import com.salpa.common.utils.StringUtils; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author salpa + */ +public class XssValidator implements ConstraintValidator +{ + 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) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} \ No newline at end of file diff --git a/salpa-framework/pom.xml b/salpa-framework/pom.xml new file mode 100644 index 0000000..c299289 --- /dev/null +++ b/salpa-framework/pom.xml @@ -0,0 +1,64 @@ + + + + salpa + com.salpa + 3.8.3 + + 4.0.0 + + salpa-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + druid-spring-boot-starter + + + + + com.github.penggle + kaptcha + + + javax.servlet-api + javax.servlet + + + + + + + com.github.oshi + oshi-core + + + + + com.salpa + salpa-system + + + + + \ No newline at end of file diff --git a/salpa-framework/salpa-framework.iml b/salpa-framework/salpa-framework.iml new file mode 100644 index 0000000..e894ad4 --- /dev/null +++ b/salpa-framework/salpa-framework.iml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salpa-framework/src/main/java/com/salpa/framework/aspectj/DataScopeAspect.java b/salpa-framework/src/main/java/com/salpa/framework/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..f6ded09 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,149 @@ +package com.salpa.framework.aspectj; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import com.salpa.common.annotation.DataScope; +import com.salpa.common.core.domain.BaseEntity; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.SecurityUtils; + +/** + * 数据过滤处理 + * + * @author salpa + */ +@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()) + { + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias()); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param userAlias 别名 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) + { + StringBuilder sqlString = new StringBuilder(); + + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + if (DATA_SCOPE_ALL.equals(dataScope)) + { + sqlString = new StringBuilder(); + break; + } + else if (DATA_SCOPE_CUSTOM.equals(dataScope)) + { + 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)); + } + } + } + + 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/salpa-framework/src/main/java/com/salpa/framework/aspectj/DataSourceAspect.java b/salpa-framework/src/main/java/com/salpa/framework/aspectj/DataSourceAspect.java new file mode 100644 index 0000000..edf2b02 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,72 @@ +package com.salpa.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.salpa.common.annotation.DataSource; +import com.salpa.common.utils.StringUtils; +import com.salpa.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * + * @author salpa + */ +@Aspect +@Order(1) +@Component +public class DataSourceAspect +{ + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.salpa.common.annotation.DataSource)" + + "|| @within(com.salpa.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/salpa-framework/src/main/java/com/salpa/framework/aspectj/LogAspect.java b/salpa-framework/src/main/java/com/salpa/framework/aspectj/LogAspect.java new file mode 100644 index 0000000..f8c833a --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/aspectj/LogAspect.java @@ -0,0 +1,217 @@ +package com.salpa.framework.aspectj; + +import java.util.Collection; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.HandlerMapping; +import com.alibaba.fastjson2.JSON; +import com.salpa.common.annotation.Log; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.enums.BusinessStatus; +import com.salpa.common.enums.HttpMethod; +import com.salpa.common.utils.ServletUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.ip.IpUtils; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.framework.manager.AsyncManager; +import com.salpa.framework.manager.factory.AsyncFactory; +import com.salpa.system.domain.SysOperLog; + +/** + * 操作日志记录处理 + * + * @author salpa + */ +@Aspect +@Component +public class LogAspect +{ + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** + * 处理完请求后执行 + * + * @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(ServletUtils.getRequest()); + operLog.setOperIp(ip); + operLog.setOperUrl(ServletUtils.getRequest().getRequestURI()); + if (loginUser != null) + { + operLog.setOperName(loginUser.getUsername()); + } + + 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); + // 保存数据库 + AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); + } + catch (Exception exp) + { + // 记录本地异常日志 + log.error("==前置通知异常=="); + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + } + + /** + * 获取注解中对方法的描述信息 用于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); + } + // 是否需要保存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) throws Exception + { + String requestMethod = operLog.getRequestMethod(); + if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) + { + String params = argsArrayToString(joinPoint.getArgs()); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } + else + { + Map paramsMap = (Map) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) + { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) + { + for (Object o : paramsArray) + { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) + { + try + { + Object jsonObj = JSON.toJSON(o); + params += jsonObj.toString() + " "; + } + catch (Exception e) + { + } + } + } + } + return params.trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @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/salpa-framework/src/main/java/com/salpa/framework/aspectj/RateLimiterAspect.java b/salpa-framework/src/main/java/com/salpa/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..2582fe0 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,91 @@ +package com.salpa.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.salpa.common.annotation.RateLimiter; +import com.salpa.common.enums.LimitType; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.ServletUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.ip.IpUtils; + +/** + * 限流处理 + * + * @author salpa + */ +@Aspect +@Component +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + String key = rateLimiter.key(); + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List 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(), key); + } + 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(ServletUtils.getRequest())).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/salpa-framework/src/main/java/com/salpa/framework/config/ApplicationConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/ApplicationConfig.java new file mode 100644 index 0000000..1130099 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/ApplicationConfig.java @@ -0,0 +1,30 @@ +package com.salpa.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 salpa + */ +@Configuration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.salpa.**.mapper") +public class ApplicationConfig +{ + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/config/CaptchaConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/CaptchaConfig.java new file mode 100644 index 0000000..5686f18 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.salpa.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 salpa + */ +@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.salpa.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/salpa-framework/src/main/java/com/salpa/framework/config/DruidConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/DruidConfig.java new file mode 100644 index 0000000..47b64bf --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/DruidConfig.java @@ -0,0 +1,126 @@ +package com.salpa.framework.config; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +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.boot.autoconfigure.DruidDataSourceBuilder; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import com.salpa.common.enums.DataSourceType; +import com.salpa.common.utils.spring.SpringUtils; +import com.salpa.framework.config.properties.DruidProperties; +import com.salpa.framework.datasource.DynamicDataSource; + +/** + * druid 配置多数据源 + * + * @author salpa + */ +@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 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 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(javax.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("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + @Override + public void destroy() + { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/config/FastJson2JsonRedisSerializer.java b/salpa-framework/src/main/java/com/salpa/framework/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..fe36545 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,48 @@ +package com.salpa.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; + +/** + * Redis使用FastJson序列化 + * + * @author salpa + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class 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, JSONReader.Feature.SupportAutoType); + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/config/FilterConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/FilterConfig.java new file mode 100644 index 0000000..42d837e --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/FilterConfig.java @@ -0,0 +1,58 @@ +package com.salpa.framework.config; + +import java.util.HashMap; +import java.util.Map; +import javax.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.salpa.common.filter.RepeatableFilter; +import com.salpa.common.filter.XssFilter; +import com.salpa.common.utils.StringUtils; + +/** + * Filter配置 + * + * @author salpa + */ +@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 initParameters = new HashMap(); + 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/salpa-framework/src/main/java/com/salpa/framework/config/KaptchaTextCreator.java b/salpa-framework/src/main/java/com/salpa/framework/config/KaptchaTextCreator.java new file mode 100644 index 0000000..ab39337 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/KaptchaTextCreator.java @@ -0,0 +1,75 @@ +package com.salpa.framework.config; + +import java.util.Random; +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author salpa + */ +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 = (int) Math.round(Math.random() * 2); + 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 (randomoperands == 2) + { + 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]); + } + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/salpa-framework/src/main/java/com/salpa/framework/config/MyBatisConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/MyBatisConfig.java new file mode 100644 index 0000000..d7831d1 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/MyBatisConfig.java @@ -0,0 +1,132 @@ +package com.salpa.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.salpa.common.utils.StringUtils; + +/** + * Mybatis支持*匹配扫描包 + * + * @author salpa + */ +@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 allResult = new ArrayList(); + try + { + for (String aliasesPackage : typeAliasesPackage.split(",")) + { + List result = new ArrayList(); + 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 hashResult = new HashSet(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 resources = new ArrayList(); + 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/salpa-framework/src/main/java/com/salpa/framework/config/RedisConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/RedisConfig.java new file mode 100644 index 0000000..aadcf8a --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/RedisConfig.java @@ -0,0 +1,69 @@ +package com.salpa.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 salpa + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport +{ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate 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 limitScript() + { + DefaultRedisScript 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/salpa-framework/src/main/java/com/salpa/framework/config/ResourcesConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/ResourcesConfig.java new file mode 100644 index 0000000..04445f5 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/ResourcesConfig.java @@ -0,0 +1,70 @@ +package com.salpa.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.Constants; +import com.salpa.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 通用配置 + * + * @author salpa + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer +{ + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** 本地文件上传路径 */ + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + SalpaConfig.getProfile() + "/"); + + /** swagger配置 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); + } + + /** + * 自定义拦截规则 + */ + @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/salpa-framework/src/main/java/com/salpa/framework/config/SecurityConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/SecurityConfig.java new file mode 100644 index 0000000..a47609f --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/SecurityConfig.java @@ -0,0 +1,146 @@ +package com.salpa.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +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.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; +import com.salpa.framework.config.properties.PermitAllUrlProperties; +import com.salpa.framework.security.filter.JwtAuthenticationTokenFilter; +import com.salpa.framework.security.handle.AuthenticationEntryPointImpl; +import com.salpa.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author salpa + */ +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter +{ + /** + * 自定义用户认证逻辑 + */ + @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; + + /** + * 解决 无法直接注入 AuthenticationManager + * + * @return + * @throws Exception + */ + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception + { + return super.authenticationManagerBean(); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception + { + // 注解标记允许匿名访问的url + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); + permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); + + httpSecurity + // CSRF禁用,因为不使用session + .csrf().disable() + // 认证失败处理类 + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + // 基于token,所以不需要session + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + // 过滤请求 + .authorizeRequests() + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + .antMatchers("/login", "/register", "/captchaImage").anonymous() + // 静态资源,可匿名访问 + .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() + .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated() + .and() + .headers().frameOptions().disable(); + // 添加Logout filter + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); + // 添加JWT filter + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + // 添加CORS filter + httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); + httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() + { + return new BCryptPasswordEncoder(); + } + + /** + * 身份认证接口 + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception + { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/config/ServerConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/ServerConfig.java new file mode 100644 index 0000000..c25eef1 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/ServerConfig.java @@ -0,0 +1,32 @@ +package com.salpa.framework.config; + +import javax.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.salpa.common.utils.ServletUtils; + +/** + * 服务相关配置 + * + * @author salpa + */ +@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/salpa-framework/src/main/java/com/salpa/framework/config/ThreadPoolConfig.java b/salpa-framework/src/main/java/com/salpa/framework/config/ThreadPoolConfig.java new file mode 100644 index 0000000..0082a36 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/ThreadPoolConfig.java @@ -0,0 +1,63 @@ +package com.salpa.framework.config; + +import com.salpa.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 salpa + **/ +@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/salpa-framework/src/main/java/com/salpa/framework/config/properties/DruidProperties.java b/salpa-framework/src/main/java/com/salpa/framework/config/properties/DruidProperties.java new file mode 100644 index 0000000..f7ef6a3 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/properties/DruidProperties.java @@ -0,0 +1,77 @@ +package com.salpa.framework.config.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import com.alibaba.druid.pool.DruidDataSource; + +/** + * druid 配置属性 + * + * @author salpa + */ +@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.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.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/salpa-framework/src/main/java/com/salpa/framework/config/properties/PermitAllUrlProperties.java b/salpa-framework/src/main/java/com/salpa/framework/config/properties/PermitAllUrlProperties.java new file mode 100644 index 0000000..0228929 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/config/properties/PermitAllUrlProperties.java @@ -0,0 +1,72 @@ +package com.salpa.framework.config.properties; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +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.salpa.common.annotation.Anonymous; + +/** + * 设置Anonymous注解允许匿名访问的url + * + * @author salpa + */ +@Configuration +public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware +{ + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private ApplicationContext applicationContext; + + private List urls = new ArrayList<>(); + + public String ASTERISK = "*"; + + @Override + public void afterPropertiesSet() + { + RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); + Map 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 -> info.getPatternsCondition().getPatterns() + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + + // 获取类上边的注解, 替代path variable 为 * + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns() + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + }); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException + { + this.applicationContext = context; + } + + public List getUrls() + { + return urls; + } + + public void setUrls(List urls) + { + this.urls = urls; + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/datasource/DynamicDataSource.java b/salpa-framework/src/main/java/com/salpa/framework/datasource/DynamicDataSource.java new file mode 100644 index 0000000..d256945 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/datasource/DynamicDataSource.java @@ -0,0 +1,26 @@ +package com.salpa.framework.datasource; + +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * + * @author salpa + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + public DynamicDataSource(DataSource defaultTargetDataSource, Map 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/salpa-framework/src/main/java/com/salpa/framework/datasource/DynamicDataSourceContextHolder.java b/salpa-framework/src/main/java/com/salpa/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..a0dabe2 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,45 @@ +package com.salpa.framework.datasource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数据源切换处理 + * + * @author salpa + */ +public class DynamicDataSourceContextHolder +{ + public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal 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/salpa-framework/src/main/java/com/salpa/framework/interceptor/RepeatSubmitInterceptor.java b/salpa-framework/src/main/java/com/salpa/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 0000000..bf9d303 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,55 @@ +package com.salpa.framework.interceptor; + +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.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.salpa.common.annotation.RepeatSubmit; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.utils.ServletUtils; + +/** + * 防止重复提交拦截器 + * + * @author salpa + */ +@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 + * @return + * @throws Exception + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/interceptor/impl/SameUrlDataInterceptor.java b/salpa-framework/src/main/java/com/salpa/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 0000000..0f52ca5 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,110 @@ +package com.salpa.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.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.salpa.common.annotation.RepeatSubmit; +import com.salpa.common.constant.CacheConstants; +import com.salpa.common.core.redis.RedisCache; +import com.salpa.common.filter.RepeatedlyRequestWrapper; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.http.HttpHelper; +import com.salpa.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 判断请求url和数据是否和上一次相同, + * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 + * + * @author salpa + */ +@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 nowDataMap = new HashMap(); + 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 sessionMap = (Map) sessionObj; + if (sessionMap.containsKey(url)) + { + Map preDataMap = (Map) sessionMap.get(url); + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + return true; + } + } + } + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); + return false; + } + + /** + * 判断参数是否相同 + */ + private boolean compareParams(Map nowMap, Map preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 判断两次间隔时间 + */ + private boolean compareTime(Map nowMap, Map 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/salpa-framework/src/main/java/com/salpa/framework/manager/AsyncManager.java b/salpa-framework/src/main/java/com/salpa/framework/manager/AsyncManager.java new file mode 100644 index 0000000..2d15c1d --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +package com.salpa.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.salpa.common.utils.Threads; +import com.salpa.common.utils.spring.SpringUtils; + +/** + * 异步任务管理器 + * + * @author salpa + */ +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/salpa-framework/src/main/java/com/salpa/framework/manager/ShutdownManager.java b/salpa-framework/src/main/java/com/salpa/framework/manager/ShutdownManager.java new file mode 100644 index 0000000..b0174fa --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/manager/ShutdownManager.java @@ -0,0 +1,39 @@ +package com.salpa.framework.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import javax.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author salpa + */ +@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/salpa-framework/src/main/java/com/salpa/framework/manager/factory/AsyncFactory.java b/salpa-framework/src/main/java/com/salpa/framework/manager/factory/AsyncFactory.java new file mode 100644 index 0000000..45bab21 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.salpa.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.salpa.common.constant.Constants; +import com.salpa.common.utils.LogUtils; +import com.salpa.common.utils.ServletUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.ip.AddressUtils; +import com.salpa.common.utils.ip.IpUtils; +import com.salpa.common.utils.spring.SpringUtils; +import com.salpa.system.domain.SysLogininfor; +import com.salpa.system.domain.SysOperLog; +import com.salpa.system.service.ISysLogininforService; +import com.salpa.system.service.ISysOperLogService; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 异步工厂(产生任务用) + * + * @author salpa + */ +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(ServletUtils.getRequest()); + 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/salpa-framework/src/main/java/com/salpa/framework/security/filter/JwtAuthenticationTokenFilter.java b/salpa-framework/src/main/java/com/salpa/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..ec67265 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,44 @@ +package com.salpa.framework.security.filter; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.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.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.framework.web.service.TokenService; + +/** + * token过滤器 验证token有效性 + * + * @author salpa + */ +@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/salpa-framework/src/main/java/com/salpa/framework/security/handle/AuthenticationEntryPointImpl.java b/salpa-framework/src/main/java/com/salpa/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..da82cf1 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,34 @@ +package com.salpa.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; +import javax.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.salpa.common.constant.HttpStatus; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.utils.ServletUtils; +import com.salpa.common.utils.StringUtils; + +/** + * 认证失败处理类 返回未授权 + * + * @author salpa + */ +@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/salpa-framework/src/main/java/com/salpa/framework/security/handle/LogoutSuccessHandlerImpl.java b/salpa-framework/src/main/java/com/salpa/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 0000000..c66cf4b --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,53 @@ +package com.salpa.framework.security.handle; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.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.salpa.common.constant.Constants; +import com.salpa.common.constant.HttpStatus; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.utils.ServletUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.framework.manager.AsyncManager; +import com.salpa.framework.manager.factory.AsyncFactory; +import com.salpa.framework.web.service.TokenService; + +/** + * 自定义退出处理类 返回成功 + * + * @author salpa + */ +@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, "退出成功")); + } + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功"))); + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/web/domain/Server.java b/salpa-framework/src/main/java/com/salpa/framework/web/domain/Server.java new file mode 100644 index 0000000..4966446 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/domain/Server.java @@ -0,0 +1,240 @@ +package com.salpa.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.salpa.common.utils.Arith; +import com.salpa.common.utils.ip.IpUtils; +import com.salpa.framework.web.domain.server.Cpu; +import com.salpa.framework.web.domain.server.Jvm; +import com.salpa.framework.web.domain.server.Mem; +import com.salpa.framework.web.domain.server.Sys; +import com.salpa.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 salpa + */ +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 sysFiles = new LinkedList(); + + 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 getSysFiles() + { + return sysFiles; + } + + public void setSysFiles(List 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 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/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Cpu.java b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Cpu.java new file mode 100644 index 0000000..3592912 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +package com.salpa.framework.web.domain.server; + +import com.salpa.common.utils.Arith; + +/** + * CPU相关信息 + * + * @author salpa + */ +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/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Jvm.java b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Jvm.java new file mode 100644 index 0000000..87ea130 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Jvm.java @@ -0,0 +1,130 @@ +package com.salpa.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.salpa.common.utils.Arith; +import com.salpa.common.utils.DateUtils; + +/** + * JVM相关信息 + * + * @author salpa + */ +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.getDatePoor(DateUtils.getNowDate(), DateUtils.getServerStartDate()); + } + + /** + * 运行参数 + */ + public String getInputArgs() + { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Mem.java b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Mem.java new file mode 100644 index 0000000..5477986 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +package com.salpa.framework.web.domain.server; + +import com.salpa.common.utils.Arith; + +/** + * 內存相关信息 + * + * @author salpa + */ +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/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Sys.java b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Sys.java new file mode 100644 index 0000000..a11f8a8 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +package com.salpa.framework.web.domain.server; + +/** + * 系统相关信息 + * + * @author salpa + */ +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/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/SysFile.java b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/SysFile.java new file mode 100644 index 0000000..01fd432 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +package com.salpa.framework.web.domain.server; + +/** + * 系统文件相关信息 + * + * @author salpa + */ +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/salpa-framework/src/main/java/com/salpa/framework/web/exception/GlobalExceptionHandler.java b/salpa-framework/src/main/java/com/salpa/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..f6d1057 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,114 @@ +package com.salpa.framework.web.exception; + +import javax.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.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import com.salpa.common.constant.HttpStatus; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.exception.DemoModeException; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.StringUtils; + +/** + * 全局异常处理器 + * + * @author salpa + */ +@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(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/salpa-framework/src/main/java/com/salpa/framework/web/service/PermissionService.java b/salpa-framework/src/main/java/com/salpa/framework/web/service/PermissionService.java new file mode 100644 index 0000000..ca10f81 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/service/PermissionService.java @@ -0,0 +1,165 @@ +package com.salpa.framework.web.service; + +import java.util.Set; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; + +/** + * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母 + * + * @author salpa + */ +@Service("ss") +public class PermissionService +{ + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** 管理员角色权限标识 */ + private static final String SUPER_ADMIN = "admin"; + + private static final String ROLE_DELIMETER = ","; + + private static final String PERMISSION_DELIMETER = ","; + + /** + * 验证用户是否具备某权限 + * + * @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; + } + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public boolean lacksPermi(String permission) + { + return hasPermi(permission) != true; + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_NAMES_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; + } + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(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 (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(ROLE_DELIMETER)) + { + if (hasRole(role)) + { + return true; + } + } + return false; + } + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set permissions, String permission) + { + return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/web/service/SysLoginService.java b/salpa-framework/src/main/java/com/salpa/framework/web/service/SysLoginService.java new file mode 100644 index 0000000..7752d15 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/service/SysLoginService.java @@ -0,0 +1,135 @@ +package com.salpa.framework.web.service; + +import javax.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.salpa.common.constant.CacheConstants; +import com.salpa.common.constant.Constants; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.core.redis.RedisCache; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.exception.user.CaptchaException; +import com.salpa.common.exception.user.CaptchaExpireException; +import com.salpa.common.exception.user.UserPasswordNotMatchException; +import com.salpa.common.utils.DateUtils; +import com.salpa.common.utils.MessageUtils; +import com.salpa.common.utils.ServletUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.ip.IpUtils; +import com.salpa.framework.manager.AsyncManager; +import com.salpa.framework.manager.factory.AsyncFactory; +import com.salpa.system.service.ISysConfigService; +import com.salpa.system.service.ISysUserService; + +/** + * 登录校验方法 + * + * @author salpa + */ +@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) + { + boolean captchaOnOff = configService.selectCaptchaOnOff(); + // 验证码开关 + if (captchaOnOff) + { + validateCaptcha(username, code, uuid); + } + // 用户验证 + Authentication authentication = null; + try + { + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager + .authenticate(new UsernamePasswordAuthenticationToken(username, password)); + } + 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()); + } + } + 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) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); + sysUser.setLoginDate(DateUtils.getNowDate()); + userService.updateUserProfile(sysUser); + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/web/service/SysPermissionService.java b/salpa-framework/src/main/java/com/salpa/framework/web/service/SysPermissionService.java new file mode 100644 index 0000000..e4bdab5 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/service/SysPermissionService.java @@ -0,0 +1,66 @@ +package com.salpa.framework.web.service; + +import java.util.HashSet; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.system.service.ISysMenuService; +import com.salpa.system.service.ISysRoleService; + +/** + * 用户权限处理 + * + * @author salpa + */ +@Component +public class SysPermissionService +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + public Set getRolePermission(SysUser user) + { + Set roles = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + roles.add("admin"); + } + else + { + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + public Set getMenuPermission(SysUser user) + { + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + perms.add("*:*:*"); + } + else + { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + return perms; + } +} diff --git a/salpa-framework/src/main/java/com/salpa/framework/web/service/SysRegisterService.java b/salpa-framework/src/main/java/com/salpa/framework/web/service/SysRegisterService.java new file mode 100644 index 0000000..fa229a0 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/service/SysRegisterService.java @@ -0,0 +1,116 @@ +package com.salpa.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.salpa.common.constant.CacheConstants; +import com.salpa.common.constant.Constants; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.domain.model.RegisterBody; +import com.salpa.common.core.redis.RedisCache; +import com.salpa.common.exception.user.CaptchaException; +import com.salpa.common.exception.user.CaptchaExpireException; +import com.salpa.common.utils.MessageUtils; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.framework.manager.AsyncManager; +import com.salpa.framework.manager.factory.AsyncFactory; +import com.salpa.system.service.ISysConfigService; +import com.salpa.system.service.ISysUserService; + +/** + * 注册校验方法 + * + * @author salpa + */ +@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(); + + boolean captchaOnOff = configService.selectCaptchaOnOff(); + // 验证码开关 + if (captchaOnOff) + { + 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 (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username))) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + 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/salpa-framework/src/main/java/com/salpa/framework/web/service/TokenService.java b/salpa-framework/src/main/java/com/salpa/framework/web/service/TokenService.java new file mode 100644 index 0000000..d1f1dea --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/service/TokenService.java @@ -0,0 +1,226 @@ +package com.salpa.framework.web.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.salpa.common.constant.CacheConstants; +import com.salpa.common.constant.Constants; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.core.redis.RedisCache; +import com.salpa.common.utils.ServletUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.ip.AddressUtils; +import com.salpa.common.utils.ip.IpUtils; +import com.salpa.common.utils.uuid.IdUtils; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +/** + * token验证处理 + * + * @author salpa + */ +@Component +public class TokenService +{ + // 令牌自定义标识 + @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) + { + } + } + 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 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(ServletUtils.getRequest()); + 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 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/salpa-framework/src/main/java/com/salpa/framework/web/service/UserDetailsServiceImpl.java b/salpa-framework/src/main/java/com/salpa/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..7103fd8 --- /dev/null +++ b/salpa-framework/src/main/java/com/salpa/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,60 @@ +package com.salpa.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.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.enums.UserStatus; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.service.ISysUserService; + +/** + * 用户验证处理 + * + * @author salpa + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService +{ + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private ISysUserService userService; + + @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("登录用户:" + username + " 不存在"); + } + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); + } + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException("对不起,您的账号:" + username + " 已停用"); + } + + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) + { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } +} diff --git a/salpa-generator/pom.xml b/salpa-generator/pom.xml new file mode 100644 index 0000000..9fdfc48 --- /dev/null +++ b/salpa-generator/pom.xml @@ -0,0 +1,40 @@ + + + + salpa + com.salpa + 3.8.3 + + 4.0.0 + + salpa-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + commons-collections + commons-collections + + + + + com.salpa + salpa-common + + + + + \ No newline at end of file diff --git a/salpa-generator/salpa-generator.iml b/salpa-generator/salpa-generator.iml new file mode 100644 index 0000000..638f9b4 --- /dev/null +++ b/salpa-generator/salpa-generator.iml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salpa-generator/src/main/java/com/salpa/generator/config/GenConfig.java b/salpa-generator/src/main/java/com/salpa/generator/config/GenConfig.java new file mode 100644 index 0000000..d3116e3 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/config/GenConfig.java @@ -0,0 +1,73 @@ +package com.salpa.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 salpa + */ +@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/salpa-generator/src/main/java/com/salpa/generator/controller/GenController.java b/salpa-generator/src/main/java/com/salpa/generator/controller/GenController.java new file mode 100644 index 0000000..51f7d45 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/controller/GenController.java @@ -0,0 +1,214 @@ +package com.salpa.generator.controller; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.core.text.Convert; +import com.salpa.common.enums.BusinessType; +import com.salpa.generator.domain.GenTable; +import com.salpa.generator.domain.GenTableColumn; +import com.salpa.generator.service.IGenTableColumnService; +import com.salpa.generator.service.IGenTableService; + +/** + * 代码生成 操作处理 + * + * @author salpa + */ +@RestController +@RequestMapping("/tool/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 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 tables = genTableService.selectGenTableAll(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + Map map = new HashMap(); + map.put("info", table); + map.put("rows", list); + map.put("tables", tables); + return AjaxResult.success(map); + } + + /** + * 查询数据库列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) + { + startPage(); + List 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 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 tableList = genTableService.selectDbTableListByNames(tableNames); + genTableService.importGenTable(tableList); + return AjaxResult.success(); + } + + /** + * 修改保存代码生成业务 + */ + @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 AjaxResult.success(); + } + + /** + * 删除代码生成 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable Long[] tableIds) + { + genTableService.deleteGenTableByIds(tableIds); + return AjaxResult.success(); + } + + /** + * 预览代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException + { + Map dataMap = genTableService.previewCode(tableId); + return AjaxResult.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 AjaxResult.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 AjaxResult.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/salpa-generator/src/main/java/com/salpa/generator/domain/GenTable.java b/salpa-generator/src/main/java/com/salpa/generator/domain/GenTable.java new file mode 100644 index 0000000..722e946 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/domain/GenTable.java @@ -0,0 +1,372 @@ +package com.salpa.generator.domain; + +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import org.apache.commons.lang3.ArrayUtils; +import com.salpa.common.constant.GenConstants; +import com.salpa.common.core.domain.BaseEntity; +import com.salpa.common.utils.StringUtils; + +/** + * 业务表 gen_table + * + * @author salpa + */ +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; + + /** 生成包路径 */ + @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 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 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 getColumns() + { + return columns; + } + + public void setColumns(List 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/salpa-generator/src/main/java/com/salpa/generator/domain/GenTableColumn.java b/salpa-generator/src/main/java/com/salpa/generator/domain/GenTableColumn.java new file mode 100644 index 0000000..f559388 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/domain/GenTableColumn.java @@ -0,0 +1,373 @@ +package com.salpa.generator.domain; + +import javax.validation.constraints.NotBlank; +import com.salpa.common.core.domain.BaseEntity; +import com.salpa.common.utils.StringUtils; + +/** + * 代码生成业务字段表 gen_table_column + * + * @author salpa + */ +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/salpa-generator/src/main/java/com/salpa/generator/mapper/GenTableColumnMapper.java b/salpa-generator/src/main/java/com/salpa/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 0000000..c1c3217 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,60 @@ +package com.salpa.generator.mapper; + +import java.util.List; +import com.salpa.generator.domain.GenTableColumn; + +/** + * 业务字段 数据层 + * + * @author salpa + */ +public interface GenTableColumnMapper +{ + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + public List selectDbTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List 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 genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/salpa-generator/src/main/java/com/salpa/generator/mapper/GenTableMapper.java b/salpa-generator/src/main/java/com/salpa/generator/mapper/GenTableMapper.java new file mode 100644 index 0000000..f516c75 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/mapper/GenTableMapper.java @@ -0,0 +1,83 @@ +package com.salpa.generator.mapper; + +import java.util.List; +import com.salpa.generator.domain.GenTable; + +/** + * 业务 数据层 + * + * @author salpa + */ +public interface GenTableMapper +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List 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); +} diff --git a/salpa-generator/src/main/java/com/salpa/generator/service/GenTableColumnServiceImpl.java b/salpa-generator/src/main/java/com/salpa/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 0000000..8b26be4 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,68 @@ +package com.salpa.generator.service; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.salpa.common.core.text.Convert; +import com.salpa.generator.domain.GenTableColumn; +import com.salpa.generator.mapper.GenTableColumnMapper; + +/** + * 业务字段 服务层实现 + * + * @author salpa + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + @Override + public List 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/salpa-generator/src/main/java/com/salpa/generator/service/GenTableServiceImpl.java b/salpa-generator/src/main/java/com/salpa/generator/service/GenTableServiceImpl.java new file mode 100644 index 0000000..a5ffa9c --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/service/GenTableServiceImpl.java @@ -0,0 +1,521 @@ +package com.salpa.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.salpa.common.constant.Constants; +import com.salpa.common.constant.GenConstants; +import com.salpa.common.core.text.CharsetKit; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.generator.domain.GenTable; +import com.salpa.generator.domain.GenTableColumn; +import com.salpa.generator.mapper.GenTableColumnMapper; +import com.salpa.generator.mapper.GenTableMapper; +import com.salpa.generator.util.GenUtils; +import com.salpa.generator.util.VelocityInitializer; +import com.salpa.generator.util.VelocityUtils; + +/** + * 业务 服务层实现 + * + * @author salpa + */ +@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 selectGenTableList(GenTable genTable) + { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + @Override + public List selectDbTableList(GenTable genTable) + { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @Override + public List selectDbTableListByNames(String[] tableNames) + { + return genTableMapper.selectDbTableListByNames(tableNames); + } + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + @Override + public List 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 tableList 导入表列表 + */ + @Override + @Transactional + public void importGenTable(List tableList) + { + String operName = SecurityUtils.getUsername(); + try + { + for (GenTable table : tableList) + { + String tableName = table.getTableName(); + GenUtils.initTable(table, operName); + int row = genTableMapper.insertGenTable(table); + if (row > 0) + { + // 保存列信息 + List 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 previewCode(Long tableId) + { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + 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 templates = VelocityUtils.getTemplateList(table.getTplCategory()); + 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 tableColumns = table.getColumns(); + Map tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); + + List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (StringUtils.isEmpty(dbTableColumns)) + { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + List 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 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 templates = VelocityUtils.getTemplateList(table.getTplCategory()); + 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/salpa-generator/src/main/java/com/salpa/generator/service/IGenTableColumnService.java b/salpa-generator/src/main/java/com/salpa/generator/service/IGenTableColumnService.java new file mode 100644 index 0000000..8ccbb3f --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.salpa.generator.service; + +import java.util.List; +import com.salpa.generator.domain.GenTableColumn; + +/** + * 业务字段 服务层 + * + * @author salpa + */ +public interface IGenTableColumnService +{ + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List 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/salpa-generator/src/main/java/com/salpa/generator/service/IGenTableService.java b/salpa-generator/src/main/java/com/salpa/generator/service/IGenTableService.java new file mode 100644 index 0000000..768761a --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/service/IGenTableService.java @@ -0,0 +1,121 @@ +package com.salpa.generator.service; + +import java.util.List; +import java.util.Map; +import com.salpa.generator.domain.GenTable; + +/** + * 业务 服务层 + * + * @author salpa + */ +public interface IGenTableService +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List 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 tableList 导入表列表 + */ + public void importGenTable(List tableList); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map 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/salpa-generator/src/main/java/com/salpa/generator/util/GenUtils.java b/salpa-generator/src/main/java/com/salpa/generator/util/GenUtils.java new file mode 100644 index 0000000..0044fd9 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/util/GenUtils.java @@ -0,0 +1,257 @@ +package com.salpa.generator.util; + +import java.util.Arrays; +import org.apache.commons.lang3.RegExUtils; +import com.salpa.common.constant.GenConstants; +import com.salpa.common.utils.StringUtils; +import com.salpa.generator.config.GenConfig; +import com.salpa.generator.domain.GenTable; +import com.salpa.generator.domain.GenTableColumn; + +/** + * 代码生成器 工具类 + * + * @author salpa + */ +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/salpa-generator/src/main/java/com/salpa/generator/util/VelocityInitializer.java b/salpa-generator/src/main/java/com/salpa/generator/util/VelocityInitializer.java new file mode 100644 index 0000000..6b304b2 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.salpa.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.salpa.common.constant.Constants; + +/** + * VelocityEngine工厂 + * + * @author salpa + */ +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/salpa-generator/src/main/java/com/salpa/generator/util/VelocityUtils.java b/salpa-generator/src/main/java/com/salpa/generator/util/VelocityUtils.java new file mode 100644 index 0000000..220dd79 --- /dev/null +++ b/salpa-generator/src/main/java/com/salpa/generator/util/VelocityUtils.java @@ -0,0 +1,402 @@ +package com.salpa.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.salpa.common.constant.GenConstants; +import com.salpa.common.utils.DateUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.generator.domain.GenTable; +import com.salpa.generator.domain.GenTableColumn; + +/** + * 模板处理工具类 + * + * @author salpa + */ +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())); + } + + /** + * 获取模板信息 + * + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory) + { + List templates = new ArrayList(); + 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("vm/vue/index.vue.vm"); + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add("vm/vue/index-tree.vue.vm"); + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add("vm/vue/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 getImportList(GenTable genTable) + { + List columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet importList = new HashSet(); + 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 columns = genTable.getColumns(); + Set dicts = new HashSet(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTable.getSubTable())) + { + List subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set dicts, List 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/salpa-generator/src/main/resources/generator.yml b/salpa-generator/src/main/resources/generator.yml new file mode 100644 index 0000000..5bd3dd6 --- /dev/null +++ b/salpa-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/salpa-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/salpa-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 0000000..54db494 --- /dev/null +++ b/salpa-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + insert into gen_table_column ( + 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 + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{createBy}, + sysdate() + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + update_by = #{updateBy}, + update_time = sysdate() + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/salpa-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/salpa-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..c8952d1 --- /dev/null +++ b/salpa-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, 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 + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_comment, + class_name, + tpl_category, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + create_time + )values( + #{tableName}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update gen_table + + table_name = #{tableName}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/salpa-generator/src/main/resources/vm/java/controller.java.vm b/salpa-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 0000000..2589600 --- /dev/null +++ b/salpa-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,115 @@ +package ${packageName}.controller; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.enums.BusinessType; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.salpa.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.salpa.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 AjaxResult.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 AjaxResult.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/salpa-generator/src/main/resources/vm/java/domain.java.vm b/salpa-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 0000000..6010d8c --- /dev/null +++ b/salpa-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,101 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import com.salpa.common.annotation.Excel; +#if($table.crud || $table.sub) +#elseif($table.tree) +#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/salpa-generator/src/main/resources/vm/java/mapper.java.vm b/salpa-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 0000000..7e7d7c2 --- /dev/null +++ b/salpa-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/salpa-generator/src/main/resources/vm/java/service.java.vm b/salpa-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 0000000..264882b --- /dev/null +++ b/salpa-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/salpa-generator/src/main/resources/vm/java/serviceImpl.java.vm b/salpa-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..2c058b2 --- /dev/null +++ b/salpa-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.salpa.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.salpa.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/salpa-generator/src/main/resources/vm/java/sub-domain.java.vm b/salpa-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..7640940 --- /dev/null +++ b/salpa-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,73 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import com.salpa.common.annotation.Excel; + +/** + * ${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/salpa-generator/src/main/resources/vm/js/api.js.vm b/salpa-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 0000000..9295524 --- /dev/null +++ b/salpa-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/salpa-generator/src/main/resources/vm/sql/sql.vm b/salpa-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 0000000..0575583 --- /dev/null +++ b/salpa-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/salpa-generator/src/main/resources/vm/vue/index-tree.vue.vm b/salpa-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 0000000..3def704 --- /dev/null +++ b/salpa-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,502 @@ + + + diff --git a/salpa-generator/src/main/resources/vm/vue/index.vue.vm b/salpa-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 0000000..e9a1fae --- /dev/null +++ b/salpa-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,598 @@ + + + diff --git a/salpa-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/salpa-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 0000000..862297c --- /dev/null +++ b/salpa-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,486 @@ + + + diff --git a/salpa-generator/src/main/resources/vm/vue/v3/index.vue.vm b/salpa-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 0000000..a363ef3 --- /dev/null +++ b/salpa-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,596 @@ + + + diff --git a/salpa-generator/src/main/resources/vm/vue/v3/readme.txt b/salpa-generator/src/main/resources/vm/vue/v3/readme.txt new file mode 100644 index 0000000..99239bb --- /dev/null +++ b/salpa-generator/src/main/resources/vm/vue/v3/readme.txt @@ -0,0 +1 @@ +ʹõRuoYi-Vue3ǰˣôҪһ´Ŀ¼ģindex.vue.vmindex-tree.vue.vmļϼvueĿ¼ \ No newline at end of file diff --git a/salpa-generator/src/main/resources/vm/xml/mapper.xml.vm b/salpa-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..0ceb3d8 --- /dev/null +++ b/salpa-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,135 @@ + + + + + +#foreach ($column in $columns) + +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + \ No newline at end of file diff --git a/salpa-quartz/pom.xml b/salpa-quartz/pom.xml new file mode 100644 index 0000000..a94cd6c --- /dev/null +++ b/salpa-quartz/pom.xml @@ -0,0 +1,40 @@ + + + + salpa + com.salpa + 3.8.3 + + 4.0.0 + + salpa-quartz + + + quartz定时任务 + + + + + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + + + + + + + com.salpa + salpa-common + + + + + \ No newline at end of file diff --git a/salpa-quartz/salpa-quartz.iml b/salpa-quartz/salpa-quartz.iml new file mode 100644 index 0000000..9056a3f --- /dev/null +++ b/salpa-quartz/salpa-quartz.iml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salpa-quartz/src/main/java/com/salpa/quartz/config/ScheduleConfig.java b/salpa-quartz/src/main/java/com/salpa/quartz/config/ScheduleConfig.java new file mode 100644 index 0000000..a4e89eb --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//package com.salpa.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 salpa +// */ +//@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", "1"); +// 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/salpa-quartz/src/main/java/com/salpa/quartz/controller/SysJobController.java b/salpa-quartz/src/main/java/com/salpa/quartz/controller/SysJobController.java new file mode 100644 index 0000000..3e1decf --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/controller/SysJobController.java @@ -0,0 +1,185 @@ +package com.salpa.quartz.controller; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.constant.Constants; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.exception.job.TaskException; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.quartz.domain.SysJob; +import com.salpa.quartz.service.ISysJobService; +import com.salpa.quartz.util.CronUtils; +import com.salpa.quartz.util.ScheduleUtils; + +/** + * 调度任务信息操作处理 + * + * @author salpa + */ +@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 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 list = jobService.selectJobList(sysJob); + ExcelUtil util = new ExcelUtil(SysJob.class); + util.exportExcel(response, list, "定时任务"); + } + + /** + * 获取定时任务详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return AjaxResult.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 + { + jobService.run(job); + return AjaxResult.success(); + } + + /** + * 删除定时任务 + */ + @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 AjaxResult.success(); + } +} diff --git a/salpa-quartz/src/main/java/com/salpa/quartz/controller/SysJobLogController.java b/salpa-quartz/src/main/java/com/salpa/quartz/controller/SysJobLogController.java new file mode 100644 index 0000000..2625da8 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/controller/SysJobLogController.java @@ -0,0 +1,92 @@ +package com.salpa.quartz.controller; + +import java.util.List; +import javax.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.salpa.common.annotation.Log; +import com.salpa.common.core.controller.BaseController; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.page.TableDataInfo; +import com.salpa.common.enums.BusinessType; +import com.salpa.common.utils.poi.ExcelUtil; +import com.salpa.quartz.domain.SysJobLog; +import com.salpa.quartz.service.ISysJobLogService; + +/** + * 调度日志操作处理 + * + * @author salpa + */ +@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 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 list = jobLogService.selectJobLogList(sysJobLog); + ExcelUtil util = new ExcelUtil(SysJobLog.class); + util.exportExcel(response, list, "调度日志"); + } + + /** + * 根据调度编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long jobLogId) + { + return AjaxResult.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 AjaxResult.success(); + } +} diff --git a/salpa-quartz/src/main/java/com/salpa/quartz/domain/SysJob.java b/salpa-quartz/src/main/java/com/salpa/quartz/domain/SysJob.java new file mode 100644 index 0000000..cbf2210 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/domain/SysJob.java @@ -0,0 +1,171 @@ +package com.salpa.quartz.domain; + +import java.util.Date; +import javax.validation.constraints.NotBlank; +import javax.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.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.constant.ScheduleConstants; +import com.salpa.common.core.domain.BaseEntity; +import com.salpa.common.utils.StringUtils; +import com.salpa.quartz.util.CronUtils; + +/** + * 定时任务调度表 sys_job + * + * @author salpa + */ +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/salpa-quartz/src/main/java/com/salpa/quartz/domain/SysJobLog.java b/salpa-quartz/src/main/java/com/salpa/quartz/domain/SysJobLog.java new file mode 100644 index 0000000..26cb7a9 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/domain/SysJobLog.java @@ -0,0 +1,155 @@ +package com.salpa.quartz.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.annotation.Excel; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 定时任务调度日志表 sys_job_log + * + * @author salpa + */ +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/salpa-quartz/src/main/java/com/salpa/quartz/mapper/SysJobLogMapper.java b/salpa-quartz/src/main/java/com/salpa/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 0000000..202edfc --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,64 @@ +package com.salpa.quartz.mapper; + +import java.util.List; +import com.salpa.quartz.domain.SysJobLog; + +/** + * 调度任务日志信息 数据层 + * + * @author salpa + */ +public interface SysJobLogMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List 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/salpa-quartz/src/main/java/com/salpa/quartz/mapper/SysJobMapper.java b/salpa-quartz/src/main/java/com/salpa/quartz/mapper/SysJobMapper.java new file mode 100644 index 0000000..edd5f3f --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/mapper/SysJobMapper.java @@ -0,0 +1,67 @@ +package com.salpa.quartz.mapper; + +import java.util.List; +import com.salpa.quartz.domain.SysJob; + +/** + * 调度任务信息 数据层 + * + * @author salpa + */ +public interface SysJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List 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/salpa-quartz/src/main/java/com/salpa/quartz/service/ISysJobLogService.java b/salpa-quartz/src/main/java/com/salpa/quartz/service/ISysJobLogService.java new file mode 100644 index 0000000..5bef702 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +package com.salpa.quartz.service; + +import java.util.List; +import com.salpa.quartz.domain.SysJobLog; + +/** + * 定时任务调度日志信息信息 服务层 + * + * @author salpa + */ +public interface ISysJobLogService +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List 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/salpa-quartz/src/main/java/com/salpa/quartz/service/ISysJobService.java b/salpa-quartz/src/main/java/com/salpa/quartz/service/ISysJobService.java new file mode 100644 index 0000000..3c1ed28 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +package com.salpa.quartz.service; + +import java.util.List; +import org.quartz.SchedulerException; +import com.salpa.common.exception.job.TaskException; +import com.salpa.quartz.domain.SysJob; + +/** + * 定时任务调度信息信息 服务层 + * + * @author salpa + */ +public interface ISysJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List 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 void 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/salpa-quartz/src/main/java/com/salpa/quartz/service/impl/SysJobLogServiceImpl.java b/salpa-quartz/src/main/java/com/salpa/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 0000000..1e396cc --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,87 @@ +package com.salpa.quartz.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.salpa.quartz.domain.SysJobLog; +import com.salpa.quartz.mapper.SysJobLogMapper; +import com.salpa.quartz.service.ISysJobLogService; + +/** + * 定时任务调度日志信息 服务层 + * + * @author salpa + */ +@Service +public class SysJobLogServiceImpl implements ISysJobLogService +{ + @Autowired + private SysJobLogMapper jobLogMapper; + + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + @Override + public List 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/salpa-quartz/src/main/java/com/salpa/quartz/service/impl/SysJobServiceImpl.java b/salpa-quartz/src/main/java/com/salpa/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 0000000..4a5f41e --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,254 @@ +package com.salpa.quartz.service.impl; + +import java.util.List; +import javax.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.salpa.common.constant.ScheduleConstants; +import com.salpa.common.exception.job.TaskException; +import com.salpa.quartz.domain.SysJob; +import com.salpa.quartz.mapper.SysJobMapper; +import com.salpa.quartz.service.ISysJobService; +import com.salpa.quartz.util.CronUtils; +import com.salpa.quartz.util.ScheduleUtils; + +/** + * 定时任务调度信息 服务层 + * + * @author salpa + */ +@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 jobList = jobMapper.selectJobAll(); + for (SysJob job : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List 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 void run(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + SysJob properties = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap); + } + + /** + * 新增任务 + * + * @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/salpa-quartz/src/main/java/com/salpa/quartz/task/RyTask.java b/salpa-quartz/src/main/java/com/salpa/quartz/task/RyTask.java new file mode 100644 index 0000000..2f2c3e0 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/task/RyTask.java @@ -0,0 +1,28 @@ +package com.salpa.quartz.task; + +import org.springframework.stereotype.Component; +import com.salpa.common.utils.StringUtils; + +/** + * 定时任务调度测试 + * + * @author salpa + */ +@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/salpa-quartz/src/main/java/com/salpa/quartz/util/AbstractQuartzJob.java b/salpa-quartz/src/main/java/com/salpa/quartz/util/AbstractQuartzJob.java new file mode 100644 index 0000000..f59fa7a --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,107 @@ +package com.salpa.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.salpa.common.constant.Constants; +import com.salpa.common.constant.ScheduleConstants; +import com.salpa.common.utils.ExceptionUtil; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.bean.BeanUtils; +import com.salpa.common.utils.spring.SpringUtils; +import com.salpa.quartz.domain.SysJob; +import com.salpa.quartz.domain.SysJobLog; +import com.salpa.quartz.service.ISysJobLogService; + +/** + * 抽象quartz调用 + * + * @author salpa + */ +public abstract class AbstractQuartzJob implements Job +{ + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal 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/salpa-quartz/src/main/java/com/salpa/quartz/util/CronUtils.java b/salpa-quartz/src/main/java/com/salpa/quartz/util/CronUtils.java new file mode 100644 index 0000000..c428d0a --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/util/CronUtils.java @@ -0,0 +1,63 @@ +package com.salpa.quartz.util; + +import java.text.ParseException; +import java.util.Date; +import org.quartz.CronExpression; + +/** + * cron表达式工具类 + * + * @author salpa + * + */ +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/salpa-quartz/src/main/java/com/salpa/quartz/util/JobInvokeUtil.java b/salpa-quartz/src/main/java/com/salpa/quartz/util/JobInvokeUtil.java new file mode 100644 index 0000000..65158a8 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/util/JobInvokeUtil.java @@ -0,0 +1,182 @@ +package com.salpa.quartz.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.spring.SpringUtils; +import com.salpa.quartz.domain.SysJob; + +/** + * 任务执行工具 + * + * @author salpa + */ +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 methodParams = getMethodParams(invokeTarget); + + if (!isValidClassName(beanName)) + { + Object bean = SpringUtils.getBean(beanName); + invokeMethod(bean, methodName, methodParams); + } + else + { + Object bean = Class.forName(beanName).newInstance(); + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 调用任务方法 + * + * @param bean 目标对象 + * @param methodName 方法名称 + * @param methodParams 方法参数 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams)); + method.invoke(bean, getMethodParamsValue(methodParams)); + } + else + { + Method method = bean.getClass().getDeclaredMethod(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 getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + List 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 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 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/salpa-quartz/src/main/java/com/salpa/quartz/util/QuartzDisallowConcurrentExecution.java b/salpa-quartz/src/main/java/com/salpa/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 0000000..a8f4f39 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.salpa.quartz.util; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import com.salpa.quartz.domain.SysJob; + +/** + * 定时任务处理(禁止并发执行) + * + * @author salpa + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/salpa-quartz/src/main/java/com/salpa/quartz/util/QuartzJobExecution.java b/salpa-quartz/src/main/java/com/salpa/quartz/util/QuartzJobExecution.java new file mode 100644 index 0000000..a7c6b41 --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/util/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.salpa.quartz.util; + +import org.quartz.JobExecutionContext; +import com.salpa.quartz.domain.SysJob; + +/** + * 定时任务处理(允许并发执行) + * + * @author salpa + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/salpa-quartz/src/main/java/com/salpa/quartz/util/ScheduleUtils.java b/salpa-quartz/src/main/java/com/salpa/quartz/util/ScheduleUtils.java new file mode 100644 index 0000000..ac87bff --- /dev/null +++ b/salpa-quartz/src/main/java/com/salpa/quartz/util/ScheduleUtils.java @@ -0,0 +1,134 @@ +package com.salpa.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.salpa.common.constant.Constants; +import com.salpa.common.constant.ScheduleConstants; +import com.salpa.common.exception.job.TaskException; +import com.salpa.common.exception.job.TaskException.Code; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.spring.SpringUtils; +import com.salpa.quartz.domain.SysJob; + +/** + * 定时任务工具类 + * + * @author salpa + * + */ +public class ScheduleUtils +{ + /** + * 得到quartz任务类 + * + * @param sysJob 执行计划 + * @return 具体执行任务类 + */ + private static Class 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 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)); + } + + 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]); + return StringUtils.containsAnyIgnoreCase(obj.getClass().getPackage().getName(), Constants.JOB_WHITELIST_STR); + } +} diff --git a/salpa-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/salpa-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..6955702 --- /dev/null +++ b/salpa-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + sysdate() + ) + + + \ No newline at end of file diff --git a/salpa-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/salpa-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..c2c49c0 --- /dev/null +++ b/salpa-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/salpa-system/pom.xml b/salpa-system/pom.xml new file mode 100644 index 0000000..0fe9390 --- /dev/null +++ b/salpa-system/pom.xml @@ -0,0 +1,28 @@ + + + + salpa + com.salpa + 3.8.3 + + 4.0.0 + + salpa-system + + + system系统模块 + + + + + + + com.salpa + salpa-common + + + + + \ No newline at end of file diff --git a/salpa-system/salpa-system.iml b/salpa-system/salpa-system.iml new file mode 100644 index 0000000..211f668 --- /dev/null +++ b/salpa-system/salpa-system.iml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salpa-system/src/main/java/com/salpa/model/domain/ProjectModelInfo.java b/salpa-system/src/main/java/com/salpa/model/domain/ProjectModelInfo.java new file mode 100644 index 0000000..9b4bd38 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/model/domain/ProjectModelInfo.java @@ -0,0 +1,103 @@ +package com.salpa.model.domain; + + +import java.io.Serializable; +import java.sql.Timestamp; + +/** + * 项目模型信息管理表 project_model_info + * + * @author zhuff + */ +public class ProjectModelInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** ID */ + private Integer projectId; + + /** 项目名称 */ + private String projectName; + + /** 项目描述 */ + private String projectDescription; + + /** 项目json文件存储 */ + private String jsonFile; + + /** 删除标识 */ + private boolean deleted; + + /** 创建时间 */ + private Timestamp createAt; + + /*业务字段*/ + /*组员*/ + private Long[] userIds; + private String[] userNames; + + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getProjectDescription() { + return projectDescription; + } + + public void setProjectDescription(String projectDescription) { + this.projectDescription = projectDescription; + } + + public String getJsonFile() { + return jsonFile; + } + + public void setJsonFile(String jsonFile) { + this.jsonFile = jsonFile; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public Timestamp getCreateAt() { + return createAt; + } + + public void setCreateAt(Timestamp createAt) { + this.createAt = createAt; + } + + public Long[] getUserIds() { + return userIds; + } + + public void setUserIds(Long[] userIds) { + this.userIds = userIds; + } + + public String[] getUserNames() { + return userNames; + } + + public void setUserNames(String[] userNames) { + this.userNames = userNames; + } +} diff --git a/salpa-system/src/main/java/com/salpa/model/mapper/ModelMapper.java b/salpa-system/src/main/java/com/salpa/model/mapper/ModelMapper.java new file mode 100644 index 0000000..b8f7ace --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/model/mapper/ModelMapper.java @@ -0,0 +1,37 @@ +package com.salpa.model.mapper; + +import com.salpa.model.domain.ProjectModelInfo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * @author zhuff + */ +@Mapper +public interface ModelMapper { + + String getProjectIds(Long userId); + + List getList(@Param("list") List list,@Param("projectName") String projectName); + + int checkProjectNameUnique(String projectName); + + int addProject(ProjectModelInfo projectModelInfo); + + void updateProjectIds(@Param("projectIds") String projectIds, @Param("userId") Long userId); + + int deleteProject(Integer projectId); + + int updateProject(ProjectModelInfo projectModelInfo); + + ProjectModelInfo getById(Integer projectId); + + List> getUserList(); + + int updateJsonFile(ProjectModelInfo projectModelInfo); + + void addProjectIds(@Param("projectIds") String projectIds, @Param("userId") Long userId); +} diff --git a/salpa-system/src/main/java/com/salpa/model/service/ModelService.java b/salpa-system/src/main/java/com/salpa/model/service/ModelService.java new file mode 100644 index 0000000..8efbc0d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/model/service/ModelService.java @@ -0,0 +1,34 @@ +package com.salpa.model.service; + +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.model.domain.ProjectModelInfo; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * @author zhuff + */ +public interface ModelService { + + List getList(Integer pageNum,Integer pageSize,String projectName); + + String checkProjectNameUnique(String projectName); + + int addProject(ProjectModelInfo projectModelInfo) throws IOException; + + int deleteProject(Integer projectId); + + int updateProject(ProjectModelInfo projectModelInfo); + + ProjectModelInfo getById(Integer projectId); + + List> getUserList(); + + int updateJsonFile(ProjectModelInfo projectModelInfo); + + AjaxResult connectDataSource(Map map); + + AjaxResult executeSql(Map map); +} diff --git a/salpa-system/src/main/java/com/salpa/model/service/impl/ModelServiceImpl.java b/salpa-system/src/main/java/com/salpa/model/service/impl/ModelServiceImpl.java new file mode 100644 index 0000000..c2d0811 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/model/service/impl/ModelServiceImpl.java @@ -0,0 +1,326 @@ +package com.salpa.model.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.github.pagehelper.PageHelper; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.model.domain.ProjectModelInfo; +import com.salpa.model.mapper.ModelMapper; +import com.salpa.model.service.ModelService; +import com.salpa.system.domain.SysUserRole; +import com.salpa.system.mapper.SysUserMapper; +import com.salpa.system.mapper.SysUserRoleMapper; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.*; +import java.util.*; + +@Service +public class ModelServiceImpl implements ModelService { + + + @Autowired + private ModelMapper modelMappper; + + @Autowired + private SysUserRoleMapper sysUserRoleMapper; + + @Autowired + private SysUserMapper sysUserMapper; + + /** + * 获取当前用户项目模型列表 + * @author zhuff + * @return + */ + @Override + public List getList(Integer pageNum,Integer pageSize,String projectName) { + SysUser user = SecurityUtils.getLoginUser().getUser(); + List projectList = new ArrayList<>(); + if (!user.isAdmin()){ + /*获取当前用户所对应项目id*/ + String projectIds = modelMappper.getProjectIds(user.getUserId()); + if (projectIds != null && projectIds != "") { + projectList = Arrays.asList(projectIds.split(",")); + } + } + PageHelper.startPage(pageNum,pageSize); + List list = modelMappper.getList(projectList,projectName); + /*添加项目人员*/ + for (ProjectModelInfo projectModelInfo : list) { + List userRole = sysUserRoleMapper.getUserRole(); + List userList = new ArrayList<>(); + List userNameList = new ArrayList<>(); + for (SysUserRole sysUserRole : userRole) { + String projectIds = sysUserRole.getProjectIds(); + if (projectIds!=null){ + String[] split = projectIds.split(","); + for (String projectId : split) { + if (projectId.equals(projectModelInfo.getProjectId().toString())){ + userList.add(sysUserRole.getUserId()); + userNameList.add(sysUserMapper.selectUserById(sysUserRole.getUserId()).getUserName()); + } + } + } + } + projectModelInfo.setUserIds(userList.toArray(new Long[userList.size()])); + projectModelInfo.setUserNames(userNameList.toArray(new String[userNameList.size()])); + } + return list; + } + + /** + * 校验项目模型名称是否唯一 + * @author zhuff + * @param projectName + * @return + */ + @Override + public String checkProjectNameUnique(String projectName) { + int count = modelMappper.checkProjectNameUnique(projectName); + if (count > 0) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 新增项目模型 + * @author zhuff + * @param projectModelInfo + * @return + */ + @Override + public int addProject(ProjectModelInfo projectModelInfo) throws IOException { + InputStream inputStream = new ClassPathResource("empty.json").getInputStream(); + String json = IOUtils.toString(inputStream, "UTF-8"); + Map map = JSON.parseObject(json); + map.put("name",projectModelInfo.getProjectName()); + map.put("describe",projectModelInfo.getProjectDescription()); + projectModelInfo.setJsonFile(JSONObject.toJSONString(map)); + /*新增项目模型*/ + int i = modelMappper.addProject(projectModelInfo); + /*获取新增项目模型的id*/ + Integer projectId = projectModelInfo.getProjectId(); + /*为用户添加项目权限*/ + Long[] userIds = projectModelInfo.getUserIds(); + for (Long userId : userIds) { + /*获取已有项目权限*/ + String projectIds = modelMappper.getProjectIds(userId); + if (projectIds != null && !"".equals(projectIds)){ + projectIds += ","+ projectId; + modelMappper.updateProjectIds(projectIds,userId); + } else { + if ("".equals(projectIds)) { + projectIds = projectId.toString(); + modelMappper.updateProjectIds(projectIds,userId); + } else { + projectIds = projectId.toString(); + modelMappper.addProjectIds(projectIds,userId); + } + } + } + return i; + } + + /** + * 删除项目模型 + * @author zhuff + * @param projectId + * @return + */ + @Override + public int deleteProject(Integer projectId) { + return modelMappper.deleteProject(projectId); + } + + /** + * 修改项目模型 + * @author zhuff + * @param projectModelInfo + * @return + */ + @Override + public int updateProject(ProjectModelInfo projectModelInfo) { + /*读取json,修改名称及描述*/ + ProjectModelInfo byId = modelMappper.getById(projectModelInfo.getProjectId()); + Map map = JSON.parseObject(byId.getJsonFile()); + map.put("name",projectModelInfo.getProjectName()); + map.put("describe",projectModelInfo.getProjectDescription()); + projectModelInfo.setJsonFile(JSONObject.toJSONString(map)); + /*修改项目模型*/ + int i = modelMappper.updateProject(projectModelInfo); + /*删除该项目原有人员*/ + List userRole = sysUserRoleMapper.getUserRole(); + for (SysUserRole sysUserRole : userRole) { + List projectList = new ArrayList<>(Arrays.asList(sysUserRole.getProjectIds().split(","))); + if (projectList.size()>0){ + // 获取迭代器 + Iterator it = projectList.iterator(); + while(it.hasNext()){ + String str = it.next(); + if(projectModelInfo.getProjectId().toString().equals(str)){ + it.remove(); + } + } + } + String projectIds = ""; + if (projectList.size()>0){ + for (String project : projectList) { + if (projectIds == ""){ + projectIds = project; + } else { + projectIds += "," + project; + } + } + } + modelMappper.updateProjectIds(projectIds,sysUserRole.getUserId()); + } + /*添加项目现有人员*/ + Long[] userIds = projectModelInfo.getUserIds(); + for (Long userId : userIds) { + /*获取已有项目权限*/ + String projectIds = modelMappper.getProjectIds(userId); + if (projectIds != null && !"".equals(projectIds)){ + projectIds += ","+ projectModelInfo.getProjectId(); + modelMappper.updateProjectIds(projectIds,userId); + } else { + if ("".equals(projectIds)) { + projectIds = projectModelInfo.getProjectId().toString(); + modelMappper.updateProjectIds(projectIds,userId); + } else { + projectIds = projectModelInfo.getProjectId().toString(); + modelMappper.addProjectIds(projectIds,userId); + } + } + } + return i; + } + + /** + * 根据id获取项目模型 + * @author zhuff + * @param projectId + * @return + */ + @Override + public ProjectModelInfo getById(Integer projectId) { + ProjectModelInfo projectModelInfo = modelMappper.getById(projectId); + List userRole = sysUserRoleMapper.getUserRole(); + List userList = new ArrayList<>(); + for (SysUserRole sysUserRole : userRole) { + String projectIds = sysUserRole.getProjectIds(); + if (projectIds!=null){ + String[] split = projectIds.split(","); + for (String project : split) { + if (project.equals(projectModelInfo.getProjectId().toString())){ + userList.add(sysUserRole.getUserId()); + } + } + } + } + projectModelInfo.setUserIds(userList.toArray(new Long[userList.size()])); + return projectModelInfo; + } + + /** + * 获取所有用户 + * @return + */ + @Override + public List> getUserList() { + return modelMappper.getUserList(); + } + + + /** + * 更新jsonFile + * @param projectModelInfo + * @return + */ + @Override + public int updateJsonFile(ProjectModelInfo projectModelInfo) { + return modelMappper.updateJsonFile(projectModelInfo); + } + + /** + * 测试连接数据库 + * @param map + * @return + */ + @Override + public AjaxResult connectDataSource(Map map) { + Properties info = new Properties(); + info.put("user" ,map.get("username")); + info.put("password" ,map.get("password")); + try { + Class.forName(map.get("driverName").toString()); + System.out.println("驱动加载成功"); + }catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + return AjaxResult.error("驱动加载失败"); + } + try { + DriverManager.getConnection(map.get("url").toString(), info); + return AjaxResult.success("数据库连接成功"); + }catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + return AjaxResult.error("数据库连接失败"); + } + } + + /** + * 执行ddl语句 + * @param map + * @return + */ + @Override + public AjaxResult executeSql(Map map) { + Properties info = new Properties(); + info.put("user" ,map.get("username")); + info.put("password" ,map.get("password")); + try { + Class.forName(map.get("driverName").toString()); + System.out.println("驱动加载成功"); + }catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + System.out.println("驱动加载失败"); + } + try { + Connection connection = DriverManager.getConnection(map.get("url").toString(), info); + String[] tableTypes = {"TABLE"}; + ResultSet tablesResult = connection.getMetaData() + .getTables(connection.getCatalog(), connection.getSchema(), null, tableTypes); + while (tablesResult.next()) { + return AjaxResult.error("该数据库已存在数据表,不能再次执行"); + } + Statement statement = connection.createStatement(); + String[] sqls = map.get("sql").toString().split(";"); + for (String sql : sqls) { + if (!sql.equals("\n\n") && !sql.equals("\n")) { + statement.executeUpdate(sql); + } + } + statement.close(); + tablesResult.close(); + connection.close(); + return AjaxResult.success("执行成功"); + }catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + return AjaxResult.error(e.getMessage()); + } + } +} diff --git a/salpa-system/src/main/java/com/salpa/online/service/OnlineService.java b/salpa-system/src/main/java/com/salpa/online/service/OnlineService.java new file mode 100644 index 0000000..ad73b87 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/online/service/OnlineService.java @@ -0,0 +1,15 @@ +package com.salpa.online.service; + +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.subject.domain.MonitorDataSource; + +import java.util.List; + +public interface OnlineService { + + List getDataSource(); + + AjaxResult connectDataSource(String databaseName); + + AjaxResult testExecute(String databaseName, String script); +} diff --git a/salpa-system/src/main/java/com/salpa/online/service/impl/OnlineServiceImpl.java b/salpa-system/src/main/java/com/salpa/online/service/impl/OnlineServiceImpl.java new file mode 100644 index 0000000..1dc581e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/online/service/impl/OnlineServiceImpl.java @@ -0,0 +1,116 @@ +package com.salpa.online.service.impl; + +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.online.service.OnlineService; +import com.salpa.subject.domain.MonitorDataSource; +import com.salpa.subject.mapper.DataSourcePropertyMapper; +import com.salpa.subject.mapper.DatabaseMapper; +import com.salpa.subject.service.ProjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.sql.*; +import java.util.*; + +@Service +public class OnlineServiceImpl implements OnlineService { + + @Autowired + private DatabaseMapper databaseMapper; + + @Autowired + private ProjectService projectService; + + @Autowired + private DataSourcePropertyMapper dataSourcePropertyMapper; + + /** + * 获取数据源 + * @return + */ + @Override + public List getDataSource() { + List list = databaseMapper.selectDataSourceList(); + return list; + } + + /** + * 连接数据库 + * @param databaseName + * @return + */ + @Override + public AjaxResult connectDataSource(String databaseName) { + MonitorDataSource monitorDataSource = databaseMapper.selectByDatabaseName(databaseName); + List> properties = dataSourcePropertyMapper.selectPropertyBySourceId(monitorDataSource.getId()); + Connection connection = projectService.create(monitorDataSource, properties); + List tables = new ArrayList<>(); + if (connection == null) { + return AjaxResult.error("连接失败"); + } else { + try { + String[] tableTypes = {"TABLE"}; + ResultSet tablesResult = connection.getMetaData() + .getTables(databaseName, monitorDataSource.getSchemaName(), null, tableTypes); + while (tablesResult.next()) { + tables.add(tablesResult.getString("TABLE_NAME")); + } + tablesResult.close();//关闭tablesResult对象 + connection.close();//关闭Connection对象 + } catch (Exception throwAbles) { + throwAbles.printStackTrace(); + return AjaxResult.error(throwAbles.getMessage()); + } + } + return AjaxResult.success(tables); + } + + /** + * 执行sql语句 + * @param databaseName + * @param script + * @return + */ + @Override + public AjaxResult testExecute(String databaseName, String script) { + Map> columns = new LinkedHashMap(); + MonitorDataSource monitorDataSource = databaseMapper.selectByDatabaseName(databaseName); + List> properties = dataSourcePropertyMapper.selectPropertyBySourceId(monitorDataSource.getId()); + Map reslutMap = new LinkedHashMap<>(); + List> rows = new ArrayList<>(); + try { + Connection connection = projectService.create(monitorDataSource, properties); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(script); + ResultSetMetaData metaData = resultSet.getMetaData(); + int columnCount = metaData.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + Map map = new HashMap<>(); + map.put("name",metaData.getColumnName(i+1)); + map.put("type",metaData.getColumnTypeName(i+1)); + columns.put(metaData.getColumnName(i+1),map); + } + while(resultSet.next()){ + Map row = new LinkedHashMap<>(); + for (int i = 0; i < columnCount; i++) { + Object object = resultSet.getObject(metaData.getColumnLabel(i+1)); + if (metaData.getColumnTypeName(i+1).equals("DATETIME") || metaData.getColumnTypeName(i+1).equals("TIMESTAMP") || metaData.getColumnTypeName(i+1).equals("DATE")) { + row.put(metaData.getColumnName(i+1),resultSet.getString(metaData.getColumnLabel(i+1))); + } else { + row.put(metaData.getColumnName(i+1),object); + } + } + rows.add(row); + } + resultSet.close();//关闭ResultSet对象 + statement.close();//关闭Statement对象 + connection.close();//关闭Connection对象 + reslutMap.put("columns",columns); + reslutMap.put("rows",rows); + } catch (SQLException throwAbles) { + throwAbles.printStackTrace(); + return AjaxResult.error(throwAbles.getMessage()); + } + return AjaxResult.success(reslutMap); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDataSource.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDataSource.java new file mode 100644 index 0000000..35e8ca8 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDataSource.java @@ -0,0 +1,134 @@ +package com.salpa.subject.domain; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 数据库表 monitor_data_source + * + * @author zhuff + */ +public class MonitorDataSource { + + /** id */ + private Integer id; + + /** 项目id */ + private Integer projectId; + + /** 数据库名称 */ + private String databaseName; + + /** schema名称 */ + private String schemaName; + + /** 数据库类型 */ + private String databaseType; + + /** 数据库路径 */ + private String url; + + /** 登录名 */ + private String username; + + /** 密码 */ + private String password; + + /** 修改时间 */ + private LocalDateTime updateAt; + + /** 创建时间 */ + private LocalDateTime createAt; + + /** 连接参数 */ + private Map[] properties; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + public String getDatabaseType() { + return databaseType; + } + + public void setDatabaseType(String databaseType) { + this.databaseType = databaseType; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + 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 LocalDateTime getUpdateAt() { + return updateAt; + } + + public void setUpdateAt(LocalDateTime updateAt) { + this.updateAt = updateAt; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } + + public Map[] getProperties() { + return properties; + } + + public void setProperties(Map[] properties) { + this.properties = properties; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDataSourceProperty.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDataSourceProperty.java new file mode 100644 index 0000000..78316ee --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDataSourceProperty.java @@ -0,0 +1,67 @@ +package com.salpa.subject.domain; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + * 数据库参数表 monitor_data_source_property + * + * @author zhuff + */ +public class MonitorDataSourceProperty { + + /** ID */ + private Integer id; + + /** 数据库ID */ + private Integer dataSourceId; + + /** key值 */ + private String dataSourceKey; + + /** value值 */ + private String dataSourceValue; + + /** 项目ID */ + private LocalDateTime createAt; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getDataSourceId() { + return dataSourceId; + } + + public void setDataSourceId(Integer dataSourceId) { + this.dataSourceId = dataSourceId; + } + + public String getDataSourceKey() { + return dataSourceKey; + } + + public void setDataSourceKey(String dataSourceKey) { + this.dataSourceKey = dataSourceKey; + } + + public String getDataSourceValue() { + return dataSourceValue; + } + + public void setDataSourceValue(String dataSourceValue) { + this.dataSourceValue = dataSourceValue; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDatabaseDocument.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDatabaseDocument.java new file mode 100644 index 0000000..157cd8f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDatabaseDocument.java @@ -0,0 +1,127 @@ +package com.salpa.subject.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 数据库文档表 + * @author zhuff + */ +public class MonitorDatabaseDocument { + + private Integer id; + + private Integer projectId; + + private String databaseName; + + private String schemaName; + + private String productName; + + private String productVersion; + + private Long documentVersion; + + private boolean archive; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private LocalDateTime createAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private LocalDateTime updateAt; + + private List tables; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getProductVersion() { + return productVersion; + } + + public void setProductVersion(String productVersion) { + this.productVersion = productVersion; + } + + public Long getDocumentVersion() { + return documentVersion; + } + + public void setDocumentVersion(Long documentVersion) { + this.documentVersion = documentVersion; + } + + public boolean isArchive() { + return archive; + } + + public void setArchive(boolean archive) { + this.archive = archive; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } + + public LocalDateTime getUpdateAt() { + return updateAt; + } + + public void setUpdateAt(LocalDateTime updateAt) { + this.updateAt = updateAt; + } + + public List getTables() { + return tables; + } + + public void setTables(List tables) { + this.tables = tables; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDatabaseType.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDatabaseType.java new file mode 100644 index 0000000..6038bc4 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDatabaseType.java @@ -0,0 +1,166 @@ +package com.salpa.subject.domain; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + * 数据库类型表 monitor_database_type + * + * @author zhuff + */ +public class MonitorDatabaseType { + + /** id */ + private Integer id; + + /** 数据库类型 */ + private String databaseType; + + /** 图像 */ + private String icon; + + /** 描述 */ + private String description; + + /** jdbc文件网络地址 */ + private String jdbcDriverFileUrl; + + /** jdbc文件路径 */ + private String jdbcDriverFilePath; + + /** jdbc名称 */ + private String jdbcDriverClassName; + + /** jdbcProtocol */ + private String jdbcProtocol; + + /** urlPattern */ + private String urlPattern; + + /** 删除标识 */ + private boolean deleted; + + /** 删除token */ + private Integer deletedToken; + + /** 修改时间 */ + private LocalDateTime updateAt; + + /** 创建时间 */ + private LocalDateTime createAt; + + /*业务字段,项目数量*/ + private Integer projectCount; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getDatabaseType() { + return databaseType; + } + + public void setDatabaseType(String databaseType) { + this.databaseType = databaseType; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getJdbcDriverFileUrl() { + return jdbcDriverFileUrl; + } + + public void setJdbcDriverFileUrl(String jdbcDriverFileUrl) { + this.jdbcDriverFileUrl = jdbcDriverFileUrl; + } + + public String getJdbcDriverFilePath() { + return jdbcDriverFilePath; + } + + public void setJdbcDriverFilePath(String jdbcDriverFilePath) { + this.jdbcDriverFilePath = jdbcDriverFilePath; + } + + public String getJdbcDriverClassName() { + return jdbcDriverClassName; + } + + public void setJdbcDriverClassName(String jdbcDriverClassName) { + this.jdbcDriverClassName = jdbcDriverClassName; + } + + public String getJdbcProtocol() { + return jdbcProtocol; + } + + public void setJdbcProtocol(String jdbcProtocol) { + this.jdbcProtocol = jdbcProtocol; + } + + public String getUrlPattern() { + return urlPattern; + } + + public void setUrlPattern(String urlPattern) { + this.urlPattern = urlPattern; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public Integer getDeletedToken() { + return deletedToken; + } + + public void setDeletedToken(Integer deletedToken) { + this.deletedToken = deletedToken; + } + + public LocalDateTime getUpdateAt() { + return updateAt; + } + + public void setUpdateAt(LocalDateTime updateAt) { + this.updateAt = updateAt; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } + + public Integer getProjectCount() { + return projectCount; + } + + public void setProjectCount(Integer projectCount) { + this.projectCount = projectCount; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentDescription.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentDescription.java new file mode 100644 index 0000000..4160f9c --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentDescription.java @@ -0,0 +1,90 @@ +package com.salpa.subject.domain; + +import java.time.LocalDateTime; +/** + * 描述表 monitor_document_description + * @author rhl + */ + +public class MonitorDocumentDescription { + /** ID */ + private Integer id; + /** 内容 */ + private String content; + /** 项目ID */ + private Integer projectId; + /** 表名 */ + private String tableName; + /** 列表项 */ + private String columnName; + /** 用户ID */ + private Long updateBy; + /** 更新时间 */ + private LocalDateTime updateAt; + /** 创建时间 */ + private LocalDateTime createAt; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getColumnName() { + return columnName; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + public Long getUpdateBy() { + return updateBy; + } + + public void setUpdateBy(Long updateBy) { + this.updateBy = updateBy; + } + + public LocalDateTime getUpdateAt() { + return updateAt; + } + + public void setUpdateAt(LocalDateTime updateAt) { + this.updateAt = updateAt; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentDiscussion.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentDiscussion.java new file mode 100644 index 0000000..c89d5b8 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentDiscussion.java @@ -0,0 +1,135 @@ +package com.salpa.subject.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.time.LocalDateTime; + +/** + * 数据库表 monitor_document_discussion + * @author hzl + */ +public class MonitorDocumentDiscussion { + /** id */ + private Integer id; + /** 评论内容content */ + private String content; + /** 用户id user_id */ + private Long userId; + /** 项目id project_id */ + private Integer projectId; + /** 表名称 table_name */ + private String tableName; + /** 列名称 column_name */ + private String columnName; + /** 创建时间 create_at */ + /*时间格式化*/ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private LocalDateTime createAt; + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + /** 用户名称 */ + private String userName; + /** 头像地址 */ + private String avatar; + /** 登陆人 */ + private String loginUser; + + public String getLoginUser() { + return loginUser; + } + + public void setLoginUser(String loginUser) { + this.loginUser = loginUser; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public Integer getId() { + return id; + } + + 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 void setId(Integer id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getColumnName() { + return columnName; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentTemplateProperty.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentTemplateProperty.java new file mode 100644 index 0000000..a2aae98 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorDocumentTemplateProperty.java @@ -0,0 +1,100 @@ +package com.salpa.subject.domain; + +import com.salpa.subject.domain.data.DocumentTemplatePropertyType; + +import java.time.LocalDateTime; + +/** + * monitor_document_template_property 文档模板属性 + * + * @author zhuff + */ +public class MonitorDocumentTemplateProperty { + + private Integer id; + + private String key; + + private String value; + + private String defaultValue; + + private DocumentTemplatePropertyType type; + + private LocalDateTime createAt; + + public MonitorDocumentTemplateProperty() {} + + public MonitorDocumentTemplateProperty(MonitorDocumentTemplateProperty value) { + this.id = value.id; + this.key = value.key; + this.value = value.value; + this.defaultValue = value.defaultValue; + this.type = value.type; + this.createAt = value.createAt; + } + + public MonitorDocumentTemplateProperty( + Integer id, + String key, + String value, + String defaultValue, + DocumentTemplatePropertyType type, + LocalDateTime createAt + ) { + this.id = id; + this.key = key; + this.value = value; + this.defaultValue = defaultValue; + this.type = type; + this.createAt = createAt; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public DocumentTemplatePropertyType getType() { + return type; + } + + public void setType(DocumentTemplatePropertyType type) { + this.type = type; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorOperationLog.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorOperationLog.java new file mode 100644 index 0000000..15c6c44 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorOperationLog.java @@ -0,0 +1,138 @@ +package com.salpa.subject.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + * 日志表 monitor_operation_log + * + * @author zhuff + */ +@Builder +public class MonitorOperationLog { + + /*id*/ + private Long id; + + /*用户id*/ + private Long operatorUserId; + + /*用户名*/ + private String operatorUsername; + + /*用户昵称*/ + private String operatorNickname; + + /*所属模块*/ + private String operationModule; + + /*方法名*/ + private String operationCode; + + /*日志名称*/ + private String operationName; + + /*结果*/ + private String operationResponse; + + /*操作是否成功*/ + private boolean success; + + /*涉及项目*/ + private Integer involvedProjectId; + + /*创建时间*/ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private LocalDateTime createAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getOperatorUserId() { + return operatorUserId; + } + + public void setOperatorUserId(Long operatorUserId) { + this.operatorUserId = operatorUserId; + } + + public String getOperatorUsername() { + return operatorUsername; + } + + public void setOperatorUsername(String operatorUsername) { + this.operatorUsername = operatorUsername; + } + + public String getOperatorNickname() { + return operatorNickname; + } + + public void setOperatorNickname(String operatorNickname) { + this.operatorNickname = operatorNickname; + } + + public String getOperationModule() { + return operationModule; + } + + public void setOperationModule(String operationModule) { + this.operationModule = operationModule; + } + + public String getOperationCode() { + return operationCode; + } + + public void setOperationCode(String operationCode) { + this.operationCode = operationCode; + } + + public String getOperationName() { + return operationName; + } + + public void setOperationName(String operationName) { + this.operationName = operationName; + } + + public String getOperationResponse() { + return operationResponse; + } + + public void setOperationResponse(String operationResponse) { + this.operationResponse = operationResponse; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public Integer getInvolvedProjectId() { + return involvedProjectId; + } + + public void setInvolvedProjectId(Integer involvedProjectId) { + this.involvedProjectId = involvedProjectId; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProject.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProject.java new file mode 100644 index 0000000..9a96877 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProject.java @@ -0,0 +1,108 @@ +package com.salpa.subject.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.salpa.subject.domain.vo.ProjectSyncRule; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + * 项目实体 + * + * @author zhuff + */ +public class MonitorProject implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 项目ID */ + private Integer id; + + /** 项目名称 */ + private String projectName; + + /** 项目名称 */ + private String projectDescription; + + /** 主题id */ + private Integer subjectId; + + /** 删除标识 */ + private boolean deleted; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private LocalDateTime createAt; + + /** 数据库 */ + private MonitorDataSource monitorDataSource; + + /** 数据库高级设置 */ + private ProjectSyncRule projectSyncRule; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getProjectDescription() { + return projectDescription; + } + + public void setProjectDescription(String projectDescription) { + this.projectDescription = projectDescription; + } + + public Integer getSubjectId() { + return subjectId; + } + + public void setSubjectId(Integer subjectId) { + this.subjectId = subjectId; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } + + public MonitorDataSource getMonitorDataSource() { + return monitorDataSource; + } + + public void setMonitorDataSource(MonitorDataSource monitorDataSource) { + this.monitorDataSource = monitorDataSource; + } + + public ProjectSyncRule getProjectSyncRule() { + return projectSyncRule; + } + + public void setProjectSyncRule(ProjectSyncRule projectSyncRule) { + this.projectSyncRule = projectSyncRule; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProjectSyncRule.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProjectSyncRule.java new file mode 100644 index 0000000..4bca4af --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProjectSyncRule.java @@ -0,0 +1,118 @@ +package com.salpa.subject.domain; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Arrays; + +/** + * 数据库配置表 monitor_project_sync_rule + * + * @author zhuff + */ +public class MonitorProjectSyncRule implements Serializable { + + private static final long serialVersionUID = 1L; + + /** ID */ + private Integer id; + + /** 项目ID */ + private Integer projectId; + + /** 忽略表名称 */ + private String ignoreTableNameRegexArray; + + /** 忽略字段名称 */ + private String ignoreColumnNameRegexArray; + + /** 是否定时同步 */ + private boolean autoSync; + + /** cron表达式 */ + private String autoSyncCron; + + /** 修改时间 */ + private LocalDateTime updateAt; + + /** 创建时间 */ + private LocalDateTime createAt; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getIgnoreTableNameRegexArray() { + return ignoreTableNameRegexArray; + } + + public void setIgnoreTableNameRegexArray(String ignoreTableNameRegexArray) { + this.ignoreTableNameRegexArray = ignoreTableNameRegexArray; + } + + public String getIgnoreColumnNameRegexArray() { + return ignoreColumnNameRegexArray; + } + + public void setIgnoreColumnNameRegexArray(String ignoreColumnNameRegexArray) { + this.ignoreColumnNameRegexArray = ignoreColumnNameRegexArray; + } + + public boolean isAutoSync() { + return autoSync; + } + + public void setAutoSync(boolean autoSync) { + this.autoSync = autoSync; + } + + public String getAutoSyncCron() { + return autoSyncCron; + } + + public void setAutoSyncCron(String autoSyncCron) { + this.autoSyncCron = autoSyncCron; + } + + public LocalDateTime getUpdateAt() { + return updateAt; + } + + public void setUpdateAt(LocalDateTime updateAt) { + this.updateAt = updateAt; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } + + @Override + public String toString() { + return "MonitorProjectSyncRule{" + + "id=" + id + + ", projectId=" + projectId + + ", ignoreTableNameRegexArray='" + ignoreTableNameRegexArray + '\'' + + ", ignoreColumnNameRegexArray='" + ignoreColumnNameRegexArray + '\'' + + ", autoSync=" + autoSync + + ", autoSyncCron='" + autoSyncCron + '\'' + + ", updateAt=" + updateAt + + ", createAt=" + createAt + + '}'; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProjectSyncTask.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProjectSyncTask.java new file mode 100644 index 0000000..d86c6ac --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorProjectSyncTask.java @@ -0,0 +1,106 @@ +package com.salpa.subject.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Date; + +/** + * 数据库配置表 monitor_project_sync_task + * + * @author zhuff + */ +public class MonitorProjectSyncTask { + + /** ID */ + private Integer id; + + /** 项目ID */ + private Integer projectId; + + /** 用户id */ + private Long userId; + + /** 状态 */ + private String status; + + /** 同步结果 */ + private String result; + + /** 同步时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private Date runAt; + + /** 修改时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private LocalDateTime updateAt; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private LocalDateTime createAt; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public Date getRunAt() { + return runAt; + } + + public void setRunAt(Date runAt) { + this.runAt = runAt; + } + + public LocalDateTime getUpdateAt() { + return updateAt; + } + + public void setUpdateAt(LocalDateTime updateAt) { + this.updateAt = updateAt; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorSubject.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorSubject.java new file mode 100644 index 0000000..1633574 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorSubject.java @@ -0,0 +1,82 @@ +package com.salpa.subject.domain; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + * 主题管理表 monitor_subject + * + * @author zhuff + */ +public class MonitorSubject implements Serializable { + + private static final long serialVersionUID = 1L; + + /** ID */ + private Integer id; + + /** 主题名称 */ + private String subjectName; + + /** 主题描述 */ + private String subjectDescription; + + /** 删除标识 */ + private boolean deleted; + + /** 创建时间 */ + private LocalDateTime createAt; + + /** 形目数量 */ + private Integer projectCount; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSubjectName() { + return subjectName; + } + + public void setSubjectName(String subjectName) { + this.subjectName = subjectName; + } + + public String getSubjectDescription() { + return subjectDescription; + } + + public void setSubjectDescription(String subjectDescription) { + this.subjectDescription = subjectDescription; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } + + public Integer getProjectCount() { + return projectCount; + } + + public void setProjectCount(Integer projectCount) { + this.projectCount = projectCount; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableColumnDocument.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableColumnDocument.java new file mode 100644 index 0000000..52f11ce --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableColumnDocument.java @@ -0,0 +1,206 @@ +package com.salpa.subject.domain; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * monitor_table_column_document + * @author zhuff + */ +public class MonitorTableColumnDocument implements Serializable { + + private static final long serialVersionUID = 1L; + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String name; + + private String type; + + private Integer dataType; + + private String comment; + + private String defaultValue; + + private Integer size; + + private Integer decimalDigits; + + private Boolean primaryKey; + + private String nullable; + + private String autoIncrement; + + private LocalDateTime createAt; + + public MonitorTableColumnDocument() {} + + public MonitorTableColumnDocument(MonitorTableColumnDocument value) { + this.id = value.id; + this.tableDocumentId = value.tableDocumentId; + this.databaseDocumentId = value.databaseDocumentId; + this.name = value.name; + this.type = value.type; + this.dataType = value.dataType; + this.comment = value.comment; + this.defaultValue = value.defaultValue; + this.size = value.size; + this.decimalDigits = value.decimalDigits; + this.primaryKey = value.primaryKey; + this.nullable = value.nullable; + this.autoIncrement = value.autoIncrement; + this.createAt = value.createAt; + } + + public MonitorTableColumnDocument( + Integer id, + Integer tableDocumentId, + Integer databaseDocumentId, + String name, + String type, + Integer dataType, + String comment, + String defaultValue, + Integer size, + Integer decimalDigits, + Boolean primaryKey, + String nullable, + String autoIncrement, + LocalDateTime createAt + ) { + this.id = id; + this.tableDocumentId = tableDocumentId; + this.databaseDocumentId = databaseDocumentId; + this.name = name; + this.type = type; + this.dataType = dataType; + this.comment = comment; + this.defaultValue = defaultValue; + this.size = size; + this.decimalDigits = decimalDigits; + this.primaryKey = primaryKey; + this.nullable = nullable; + this.autoIncrement = autoIncrement; + this.createAt = createAt; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getTableDocumentId() { + return tableDocumentId; + } + + public void setTableDocumentId(Integer tableDocumentId) { + this.tableDocumentId = tableDocumentId; + } + + public Integer getDatabaseDocumentId() { + return databaseDocumentId; + } + + public void setDatabaseDocumentId(Integer databaseDocumentId) { + this.databaseDocumentId = databaseDocumentId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Integer getDataType() { + return dataType; + } + + public void setDataType(Integer dataType) { + this.dataType = dataType; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public Integer getDecimalDigits() { + return decimalDigits; + } + + public void setDecimalDigits(Integer decimalDigits) { + this.decimalDigits = decimalDigits; + } + + public Boolean getPrimaryKey() { + return primaryKey; + } + + public void setPrimaryKey(Boolean primaryKey) { + this.primaryKey = primaryKey; + } + + public String getNullable() { + return nullable; + } + + public void setNullable(String nullable) { + this.nullable = nullable; + } + + public String getAutoIncrement() { + return autoIncrement; + } + + public void setAutoIncrement(String autoIncrement) { + this.autoIncrement = autoIncrement; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableDocument.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableDocument.java new file mode 100644 index 0000000..103bea3 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableDocument.java @@ -0,0 +1,173 @@ +package com.salpa.subject.domain; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public class MonitorTableDocument { + + private Integer id; + + private Integer databaseDocumentId; + + private String name; + + private String type; + + private String comment; + + private LocalDateTime createAt; + + public MonitorTableDocument() {}; + + public MonitorTableDocument(MonitorTableDocument value) { + this.id = value.id; + this.databaseDocumentId = value.databaseDocumentId; + this.name = value.name; + this.type = value.type; + this.comment = value.comment; + this.createAt = value.createAt; + } + + public MonitorTableDocument( + Integer id, + Integer databaseDocumentId, + String name, + String type, + String comment, + LocalDateTime createAt + ) { + this.id = id; + this.databaseDocumentId = databaseDocumentId; + this.name = name; + this.type = type; + this.comment = comment; + this.createAt = createAt; + } + + /*描述*/ + private String description; + + /*评论条数*/ + private Integer discussionCount; + + private List columns; + + private List indexes; + + private List foreignKeys; + + private List triggers; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getDatabaseDocumentId() { + return databaseDocumentId; + } + + public void setDatabaseDocumentId(Integer databaseDocumentId) { + this.databaseDocumentId = databaseDocumentId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getDiscussionCount() { + return discussionCount; + } + + public void setDiscussionCount(Integer discussionCount) { + this.discussionCount = discussionCount; + } + + public List getColumns() { + return columns; + } + + public void setColumns(List columns) { + this.columns = columns; + } + + public List getIndexes() { + return indexes; + } + + public void setIndexes(List indexes) { + this.indexes = indexes; + } + + public List getForeignKeys() { + return foreignKeys; + } + + public void setForeignKeys(List foreignKeys) { + this.foreignKeys = foreignKeys; + } + + public List getTriggers() { + return triggers; + } + + public void setTriggers(List triggers) { + this.triggers = triggers; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("TableDocument ("); + + sb.append(id); + sb.append(", ").append(databaseDocumentId); + sb.append(", ").append(name); + sb.append(", ").append(type); + sb.append(", ").append(comment); + sb.append(", ").append(createAt); + + sb.append(")"); + return sb.toString(); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableForeignKeyDocument.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableForeignKeyDocument.java new file mode 100644 index 0000000..b65c93e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableForeignKeyDocument.java @@ -0,0 +1,193 @@ +package com.salpa.subject.domain; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * monitor_table_foreign_key_document + * @author zhuff + */ +public class MonitorTableForeignKeyDocument implements Serializable { + + private static final long serialVersionUID = 1L; + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private Integer keySeq; + + private String fkName; + + private String fkTableName; + + private String fkColumnName; + + private String pkName; + + private String pkTableName; + + private String pkColumnName; + + private String updateRule; + + private String deleteRule; + + private LocalDateTime createAt; + + public MonitorTableForeignKeyDocument() {} + + public MonitorTableForeignKeyDocument(MonitorTableForeignKeyDocument value) { + this.id = value.id; + this.tableDocumentId = value.tableDocumentId; + this.databaseDocumentId = value.databaseDocumentId; + this.keySeq = value.keySeq; + this.fkName = value.fkName; + this.fkTableName = value.fkTableName; + this.fkColumnName = value.fkColumnName; + this.pkName = value.pkName; + this.pkTableName = value.pkTableName; + this.pkColumnName = value.pkColumnName; + this.updateRule = value.updateRule; + this.deleteRule = value.deleteRule; + this.createAt = value.createAt; + } + + public MonitorTableForeignKeyDocument( + Integer id, + Integer tableDocumentId, + Integer databaseDocumentId, + Integer keySeq, + String fkName, + String fkTableName, + String fkColumnName, + String pkName, + String pkTableName, + String pkColumnName, + String updateRule, + String deleteRule, + LocalDateTime createAt + ) { + this.id = id; + this.tableDocumentId = tableDocumentId; + this.databaseDocumentId = databaseDocumentId; + this.keySeq = keySeq; + this.fkName = fkName; + this.fkTableName = fkTableName; + this.fkColumnName = fkColumnName; + this.pkName = pkName; + this.pkTableName = pkTableName; + this.pkColumnName = pkColumnName; + this.updateRule = updateRule; + this.deleteRule = deleteRule; + this.createAt = createAt; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getTableDocumentId() { + return tableDocumentId; + } + + public void setTableDocumentId(Integer tableDocumentId) { + this.tableDocumentId = tableDocumentId; + } + + public Integer getDatabaseDocumentId() { + return databaseDocumentId; + } + + public void setDatabaseDocumentId(Integer databaseDocumentId) { + this.databaseDocumentId = databaseDocumentId; + } + + public Integer getKeySeq() { + return keySeq; + } + + public void setKeySeq(Integer keySeq) { + this.keySeq = keySeq; + } + + public String getFkName() { + return fkName; + } + + public void setFkName(String fkName) { + this.fkName = fkName; + } + + public String getFkTableName() { + return fkTableName; + } + + public void setFkTableName(String fkTableName) { + this.fkTableName = fkTableName; + } + + public String getFkColumnName() { + return fkColumnName; + } + + public void setFkColumnName(String fkColumnName) { + this.fkColumnName = fkColumnName; + } + + public String getPkName() { + return pkName; + } + + public void setPkName(String pkName) { + this.pkName = pkName; + } + + public String getPkTableName() { + return pkTableName; + } + + public void setPkTableName(String pkTableName) { + this.pkTableName = pkTableName; + } + + public String getPkColumnName() { + return pkColumnName; + } + + public void setPkColumnName(String pkColumnName) { + this.pkColumnName = pkColumnName; + } + + public String getUpdateRule() { + return updateRule; + } + + public void setUpdateRule(String updateRule) { + this.updateRule = updateRule; + } + + public String getDeleteRule() { + return deleteRule; + } + + public void setDeleteRule(String deleteRule) { + this.deleteRule = deleteRule; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableIndexDocument.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableIndexDocument.java new file mode 100644 index 0000000..e62cdca --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableIndexDocument.java @@ -0,0 +1,127 @@ +package com.salpa.subject.domain; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * @author zhuff + * monitor_table_index_document + */ +public class MonitorTableIndexDocument implements Serializable { + + private static final long serialVersionUID = 1L; + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String name; + + private Boolean unique; + + private String columnNameArray; + + private LocalDateTime createAt; + + /*业务字段*/ + private Object columnNameArrays; + + public MonitorTableIndexDocument() {} + + public MonitorTableIndexDocument(MonitorTableIndexDocument value) { + this.id = value.id; + this.tableDocumentId = value.tableDocumentId; + this.databaseDocumentId = value.databaseDocumentId; + this.name = value.name; + this.unique = value.unique; + this.columnNameArray = value.columnNameArray; + this.createAt = value.createAt; + } + + public MonitorTableIndexDocument( + Integer id, + Integer tableDocumentId, + Integer databaseDocumentId, + String name, + Boolean unique, + String columnNameArray, + LocalDateTime createAt + ) { + this.id = id; + this.tableDocumentId = tableDocumentId; + this.databaseDocumentId = databaseDocumentId; + this.name = name; + this.unique = unique; + this.columnNameArray = columnNameArray; + this.createAt = createAt; + } + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getTableDocumentId() { + return tableDocumentId; + } + + public void setTableDocumentId(Integer tableDocumentId) { + this.tableDocumentId = tableDocumentId; + } + + public Integer getDatabaseDocumentId() { + return databaseDocumentId; + } + + public void setDatabaseDocumentId(Integer databaseDocumentId) { + this.databaseDocumentId = databaseDocumentId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getUnique() { + return unique; + } + + public void setUnique(Boolean unique) { + this.unique = unique; + } + + public String getColumnNameArray() { + return columnNameArray; + } + + public void setColumnNameArray(String columnNameArray) { + this.columnNameArray = columnNameArray; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } + + public Object getColumnNameArrays() { + return columnNameArrays; + } + + public void setColumnNameArrays(Object columnNameArrays) { + this.columnNameArrays = columnNameArrays; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableTriggerDocument.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableTriggerDocument.java new file mode 100644 index 0000000..d09e537 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorTableTriggerDocument.java @@ -0,0 +1,141 @@ +package com.salpa.subject.domain; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * monitor_table_trigger_document + * @author zhuff + */ +public class MonitorTableTriggerDocument implements Serializable { + + private static final long serialVersionUID = 1L; + + private Integer id; + + private String name; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String timing; + + private String manipulation; + + private String statement; + + private String triggerCreateAt; + + private LocalDateTime createAt; + + public MonitorTableTriggerDocument() {} + + public MonitorTableTriggerDocument(MonitorTableTriggerDocument value) { + this.id = value.id; + this.name = value.name; + this.tableDocumentId = value.tableDocumentId; + this.databaseDocumentId = value.databaseDocumentId; + this.timing = value.timing; + this.manipulation = value.manipulation; + this.statement = value.statement; + this.triggerCreateAt = value.triggerCreateAt; + this.createAt = value.createAt; + } + + public MonitorTableTriggerDocument( + Integer id, + String name, + Integer tableDocumentId, + Integer databaseDocumentId, + String timing, + String manipulation, + String statement, + String triggerCreateAt, + LocalDateTime createAt + ) { + this.id = id; + this.name = name; + this.tableDocumentId = tableDocumentId; + this.databaseDocumentId = databaseDocumentId; + this.timing = timing; + this.manipulation = manipulation; + this.statement = statement; + this.triggerCreateAt = triggerCreateAt; + this.createAt = createAt; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getTableDocumentId() { + return tableDocumentId; + } + + public void setTableDocumentId(Integer tableDocumentId) { + this.tableDocumentId = tableDocumentId; + } + + public Integer getDatabaseDocumentId() { + return databaseDocumentId; + } + + public void setDatabaseDocumentId(Integer databaseDocumentId) { + this.databaseDocumentId = databaseDocumentId; + } + + public String getTiming() { + return timing; + } + + public void setTiming(String timing) { + this.timing = timing; + } + + public String getManipulation() { + return manipulation; + } + + public void setManipulation(String manipulation) { + this.manipulation = manipulation; + } + + public String getStatement() { + return statement; + } + + public void setStatement(String statement) { + this.statement = statement; + } + + public String getTriggerCreateAt() { + return triggerCreateAt; + } + + public void setTriggerCreateAt(String triggerCreateAt) { + this.triggerCreateAt = triggerCreateAt; + } + + public LocalDateTime getCreateAt() { + return createAt; + } + + public void setCreateAt(LocalDateTime createAt) { + this.createAt = createAt; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/MonitorWarning.java b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorWarning.java new file mode 100644 index 0000000..5901dc5 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/MonitorWarning.java @@ -0,0 +1,40 @@ +package com.salpa.subject.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * 数据库类型表 monitor_waening + * + * @author zff + */ +@Data +public class MonitorWarning { + + /** ID */ + private Integer id; + + /** 告警名称 */ + private String warningName; + + /** 所属主题模型 */ + private String warningSubject; + + /** 所属数据库 */ + private String warningDataSource; + + /** 告警时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date warningTime; + + /** 告警内容 */ + private String warningContent; + + /** 告警状态 */ + private Integer warningStatus; + + /** 告警处理人 */ + private String handlingUser; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/converter/JsonConverter.java b/salpa-system/src/main/java/com/salpa/subject/domain/converter/JsonConverter.java new file mode 100644 index 0000000..c790c01 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/converter/JsonConverter.java @@ -0,0 +1,81 @@ +package com.salpa.subject.domain.converter; + +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.salpa.common.core.domain.JsonData; +import com.salpa.subject.domain.data.DatabaseDocumentResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Component +public class JsonConverter { + + @Autowired + private ObjectMapper objectMapper; + + public List fromJson(String data) { + if (data == null) { + return Collections.emptyList(); + } else { + return Arrays.asList(data.split(",")); + } + } + + public JSONObject toJson(List array) { + String json = objToJson(array); + return JSONObject.parseObject(json); + } + + public JSONObject toJson(DatabaseDocumentResponse response) { + String json = objToJson(response); + return JSONObject.parseObject(json); + } + + public JSONObject toJson(JsonData data) { + String json = objToJson(data); + return JSONObject.parseObject(json); + } + + public DatabaseDocumentResponse of(JSONObject json) { + try { + if (json == null) { + return null; + } + return objectMapper.readValue(json.toString().getBytes(StandardCharsets.UTF_8), DatabaseDocumentResponse.class); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public JsonData toJsonData(JSONObject json) { + try { + if (json == null) { + return null; + } + return objectMapper.readValue(json.toString().getBytes(StandardCharsets.UTF_8), JsonData.class); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public JSONObject objToJsonData(Object obj) { + String json = objToJson(obj); + return JSONObject.parseObject(json); + } + + private String objToJson(Object obj) { + try { + return objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/ColumnMeta.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/ColumnMeta.java new file mode 100644 index 0000000..a399aa3 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/ColumnMeta.java @@ -0,0 +1,36 @@ +package com.salpa.subject.domain.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ColumnMeta { + + private String name; + + private String comment; + + private String type; + + private Integer dataType; + + /** + * if default value is empty string, will be converted to ''. + */ + private String defaultValue; + + private Integer size; + + private Integer decimalDigits; + + private String nullable; + + private String autoIncrement; + + private Boolean isPrimaryKey; +} \ No newline at end of file diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseDocumentResponse.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseDocumentResponse.java new file mode 100644 index 0000000..960d949 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseDocumentResponse.java @@ -0,0 +1,37 @@ +package com.salpa.subject.domain.data; + +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabaseDocumentResponse { + + private Integer id; + + private String databaseName; + + private String schemaName; + + private String productName; + + private String productVersion; + + private Integer documentVersion; + + @Builder.Default + private List tables = new ArrayList<>(); + + private LocalDateTime createAt; + + private DiffType diffType; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseDocumentSimpleResponse.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseDocumentSimpleResponse.java new file mode 100644 index 0000000..a508888 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseDocumentSimpleResponse.java @@ -0,0 +1,59 @@ +package com.salpa.subject.domain.data; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.salpa.subject.domain.MonitorTableColumnDocument; +import com.salpa.subject.domain.diff.DiffAble; +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Data +public class DatabaseDocumentSimpleResponse { + + private Integer id; + + private String projectName; + + private String databaseName; + + private String schemaName; + + private String productName; + + private String productVersion; + + private Long documentVersion; + + private List tables = new ArrayList<>(); + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT +8") + private LocalDateTime createAt; + + private DiffType diffType; + + @Data + public static class TableData implements DiffAble { + + private Integer id; + + private String name; + + private String type; + + private String comment; + + private List columns = new ArrayList<>(); + + private Integer discussionCount; + + private String description; + + private DiffType diffType; + + private TableData original; + + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseMeta.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseMeta.java new file mode 100644 index 0000000..74f008c --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabaseMeta.java @@ -0,0 +1,50 @@ +package com.salpa.subject.domain.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collections; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class DatabaseMeta { + + /** + * product_name + */ + private String productName; + + /** + * product_version + */ + private String productVersion; + + /** + * driver_name + */ + private String driverName; + + /** + * driver_version + */ + private String driverVersion; + + /** + * database_name + */ + private String databaseName; + + /** + * schema_name + */ + private String schemaName; + + @Builder.Default + private List tables = Collections.emptyList(); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/Databasir.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/Databasir.java new file mode 100644 index 0000000..433712d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/Databasir.java @@ -0,0 +1,58 @@ +package com.salpa.subject.domain.data; + +import com.salpa.subject.domain.provider.MetaProviders; +import com.salpa.subject.domain.provider.condition.Condition; +import com.salpa.subject.domain.render.Render; +import com.salpa.subject.domain.render.RenderConfig; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.io.IOException; +import java.io.OutputStream; +import java.sql.Connection; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Getter +public class Databasir { + + private final DatabasirConfig config; + + public Optional get(Connection connection, String databaseName, String schemaName) { + // pre compile regex + List ignoreTableColumnPatterns = config.getIgnoreTableColumnNameRegex().stream() + .map(Pattern::compile) + .collect(Collectors.toList()); + List ignoreTableNamePatterns = config.getIgnoreTableNameRegex().stream() + .map(Pattern::compile) + .collect(Collectors.toList()); + Condition condition = Condition.builder() + .databaseName(databaseName) + .schemaName(schemaName) + .ignoreTableNamePatterns(ignoreTableNamePatterns) + .ignoreTableColumnNamePatterns(ignoreTableColumnPatterns) + .build(); + return MetaProviders + .of(connection) + .select(connection, condition); + } + + public void renderAsMarkdown(DatabaseMeta meta, OutputStream out) throws IOException { + renderAsMarkdown(new RenderConfig(), meta, out); + } + + public void renderAsMarkdown(RenderConfig config, DatabaseMeta meta, OutputStream stream) throws IOException { + Render.markdownRender(config).rendering(meta, stream); + } + + public static Databasir of() { + return of(new DatabasirConfig()); + } + + public static Databasir of(DatabasirConfig config) { + return new Databasir(config); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabasirConfig.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabasirConfig.java new file mode 100644 index 0000000..f8fb184 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/DatabasirConfig.java @@ -0,0 +1,26 @@ +package com.salpa.subject.domain.data; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Collection; +import java.util.HashSet; + +@Getter +@Setter +public class DatabasirConfig { + + private Collection ignoreTableNameRegex = new HashSet<>(); + + private Collection ignoreTableColumnNameRegex = new HashSet<>(); + + public DatabasirConfig ignoreTable(String tableNameRegex) { + ignoreTableNameRegex.add(tableNameRegex); + return this; + } + + public DatabasirConfig ignoreColumn(String columnNameRegex) { + ignoreTableColumnNameRegex.add(columnNameRegex); + return this; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentTemplatePropertiesResponse.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentTemplatePropertiesResponse.java new file mode 100644 index 0000000..54e33a7 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentTemplatePropertiesResponse.java @@ -0,0 +1,47 @@ +package com.salpa.subject.domain.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DocumentTemplatePropertiesResponse { + + @Builder.Default + private List tableFieldNameProperties = Collections.emptyList(); + + @Builder.Default + private List columnFieldNameProperties = Collections.emptyList(); + + @Builder.Default + private List indexFieldNameProperties = Collections.emptyList(); + + @Builder.Default + private List triggerFieldNameProperties = Collections.emptyList(); + + @Builder.Default + private List foreignKeyFieldNameProperties = Collections.emptyList(); + + @Data + public static class DocumentTemplatePropertyResponse { + + private String key; + + private String value; + + private String defaultValue; + + private DocumentTemplatePropertyType type; + + private LocalDateTime createAt; + + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentTemplatePropertyType.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentTemplatePropertyType.java new file mode 100644 index 0000000..9d8c25e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentTemplatePropertyType.java @@ -0,0 +1,7 @@ +package com.salpa.subject.domain.data; + +public enum DocumentTemplatePropertyType { + + TABLE_FIELD_NAME, INDEX_FIELD_NAME, COLUMN_FIELD_NAME, TRIGGER_FIELD_NAME, FOREIGN_KEY_FIELD_NAME; + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentUpdated.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentUpdated.java new file mode 100644 index 0000000..cf5ef2f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/DocumentUpdated.java @@ -0,0 +1,32 @@ +package com.salpa.subject.domain.data; + +import com.salpa.subject.domain.diff.data.RootDiff; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Optional; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DocumentUpdated { + + private RootDiff diff; + + private Long newVersion; + + private Long oldVersion; + + private Integer projectId; + + private Integer databaseDocumentId; + + public Optional getOldVersion() { + return Optional.ofNullable(oldVersion); + } + + public Optional getDiff() { + return Optional.ofNullable(diff); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/ForeignKeyMeta.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/ForeignKeyMeta.java new file mode 100644 index 0000000..e7ecef1 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/ForeignKeyMeta.java @@ -0,0 +1,43 @@ +package com.salpa.subject.domain.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ForeignKeyMeta { + + private Integer keySeq; + + /** + * may null + */ + private String pkName; + + private String pkTableName; + + private String pkColumnName; + + /** + * may null + */ + private String fkName; + + private String fkTableName; + + private String fkColumnName; + + /** + * NO_ACTION \ CASCADE \ SET_NULL \ SET_DEFAULT + */ + private String updateRule; + + /** + * NO_ACTION \ CASCADE \ SET_NULL \ SET_DEFAULT + */ + private String deleteRule; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/IndexMeta.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/IndexMeta.java new file mode 100644 index 0000000..2b46ca7 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/IndexMeta.java @@ -0,0 +1,23 @@ +package com.salpa.subject.domain.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collections; +import java.util.List; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IndexMeta { + + private String name; + + @Builder.Default + private List columnNames = Collections.emptyList(); + + private Boolean isUniqueKey; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/ProjectSyncTaskStatus.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/ProjectSyncTaskStatus.java new file mode 100644 index 0000000..6caf30a --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/ProjectSyncTaskStatus.java @@ -0,0 +1,10 @@ +package com.salpa.subject.domain.data; + +public enum ProjectSyncTaskStatus { + + NEW, + RUNNING, + FINISHED, + FAILED, + CANCELED +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/TableDocumentResponse.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/TableDocumentResponse.java new file mode 100644 index 0000000..fe24551 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/TableDocumentResponse.java @@ -0,0 +1,170 @@ +package com.salpa.subject.domain.data; + + +import com.salpa.subject.domain.diff.DiffAble; +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TableDocumentResponse { + + private Integer id; + + private Integer databaseDocumentId; + + private String name; + + private String type; + + private String comment; + + private Integer discussionCount; + + private String description; + + @Builder.Default + private List columns = new ArrayList<>(); + + @Builder.Default + private List indexes = new ArrayList<>(); + + @Builder.Default + private List foreignKeys = new ArrayList<>(); + + @Builder.Default + private List triggers = new ArrayList<>(); + + private LocalDateTime createAt; + + private DiffType diffType; + + private TableDocumentResponse original; + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class ColumnDocumentResponse implements DiffAble { + private Integer id; + + private String name; + + private String type; + + private Integer size; + + private Integer decimalDigits; + + private String comment; + + private String description; + + private Boolean isPrimaryKey; + + private String nullable; + + private String autoIncrement; + + private String defaultValue; + + private LocalDateTime createAt; + + private DiffType diffType; + + private Integer discussionCount; + + private ColumnDocumentResponse original; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class IndexDocumentResponse implements DiffAble { + + private Integer id; + + private String name; + + private Boolean isUnique; + + @Builder.Default + private List columnNames = new ArrayList<>(); + + private String columnNameArray; + + private LocalDateTime createAt; + + private DiffType diffType; + + private IndexDocumentResponse original; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class ForeignKeyDocumentResponse implements DiffAble { + + private Integer id; + + private String fkName; + + private String fkTableName; + + private String fkColumnName; + + private Integer keySeq; + + private String pkName; + + private String pkTableName; + + private String pkColumnName; + + private String updateRule; + + private String deleteRule; + + private LocalDateTime createAt; + + private DiffType diffType; + + private ForeignKeyDocumentResponse original; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class TriggerDocumentResponse implements DiffAble { + + private Integer id; + + private String name; + + private String timing; + + private String manipulation; + + private String statement; + + private String triggerCreateAt; + + private LocalDateTime createAt; + + private DiffType diffType; + + private TriggerDocumentResponse original; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/TableMeta.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/TableMeta.java new file mode 100644 index 0000000..09494cb --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/TableMeta.java @@ -0,0 +1,34 @@ +package com.salpa.subject.domain.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collections; +import java.util.List; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TableMeta { + + private String name; + + private String type; + + private String comment; + + @Builder.Default + private List columns = Collections.emptyList(); + + @Builder.Default + private List triggers = Collections.emptyList(); + + @Builder.Default + private List indexes = Collections.emptyList(); + + @Builder.Default + private List foreignKeys = Collections.emptyList(); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/data/TriggerMeta.java b/salpa-system/src/main/java/com/salpa/subject/domain/data/TriggerMeta.java new file mode 100644 index 0000000..1241d6e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/data/TriggerMeta.java @@ -0,0 +1,32 @@ +package com.salpa.subject.domain.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * now: only support mysql, postgresql. + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TriggerMeta { + + private String name; + + /** + * example: BEFORE, AFTER + */ + private String timing; + + /** + * example: INSERT, UPDATE + */ + private String manipulation; + + private String statement; + + private String createAt; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/BaseTypeFieldEqualFunction.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/BaseTypeFieldEqualFunction.java new file mode 100644 index 0000000..2f4cb54 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/BaseTypeFieldEqualFunction.java @@ -0,0 +1,82 @@ +package com.salpa.subject.domain.diff; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Slf4j +public class BaseTypeFieldEqualFunction implements BiFunction { + + private final List ignoreFields; + + @Override + public Boolean apply(Object that, Object other) { + if (Objects.equals(that, other)) { + return true; + } + if (that == null || other == null) { + return false; + } + try { + BeanInfo thatBean = Introspector.getBeanInfo(that.getClass()); + BeanInfo otherBean = Introspector.getBeanInfo(other.getClass()); + Map otherBeanPropertyMap = Arrays.stream(otherBean.getPropertyDescriptors()) + .collect(Collectors.toMap(PropertyDescriptor::getName, p -> p)); + for (PropertyDescriptor thatProperty : thatBean.getPropertyDescriptors()) { + if (thatProperty.getReadMethod() == null || thatProperty.getWriteMethod() == null) { + continue; + } + if (ignoreFields.contains(thatProperty.getName())) { + continue; + } + if (!otherBeanPropertyMap.containsKey(thatProperty.getName())) { + return false; + } + if (Collection.class.isAssignableFrom(thatProperty.getPropertyType())) { + Collection thatValue = (Collection) thatProperty.getReadMethod().invoke(that); + Collection otherValue = (Collection) otherBeanPropertyMap.get(thatProperty.getName()) + .getReadMethod().invoke(other); + return handleCollection(thatValue, otherValue); + } + if (!thatProperty.getPropertyType().isPrimitive()) { + Object thatValue = thatProperty.getReadMethod().invoke(that); + Object otherValue = otherBeanPropertyMap.get(thatProperty.getName()).getReadMethod().invoke(other); + if (!apply(thatValue, otherValue)) { + return false; + } + } + Object thatValue = thatProperty.getReadMethod().invoke(that); + Object otherValue = otherBeanPropertyMap.get(thatProperty.getName()).getReadMethod().invoke(other); + if (!Objects.equals(thatValue, otherValue)) { + return false; + } + } + } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) { + log.error("Error comparing objects", e); + throw new RuntimeException(e); + } + return true; + } + + private boolean handleCollection(Collection that, Collection other) { + if (that.size() != other.size()) { + return false; + } + for (Object thatObj : that) { + boolean anyMatch = other.stream().anyMatch(otherObj -> this.apply(thatObj, otherObj)); + if (!anyMatch) { + return false; + } + } + return true; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/ColumnDocDiff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/ColumnDocDiff.java new file mode 100644 index 0000000..164b186 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/ColumnDocDiff.java @@ -0,0 +1,42 @@ +package com.salpa.subject.domain.diff; + +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class ColumnDocDiff implements DiffAble { + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String name; + + private String type; + + private Integer dataType; + + private String comment; + + private String defaultValue; + + private Integer size; + + private Integer decimalDigits; + + private Boolean isPrimaryKey; + + private String nullable; + + private String autoIncrement; + + private LocalDateTime createAt; + + private DiffType diffType; + + private ColumnDocDiff original; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/DatabaseDocDiff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/DatabaseDocDiff.java new file mode 100644 index 0000000..905839f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/DatabaseDocDiff.java @@ -0,0 +1,32 @@ +package com.salpa.subject.domain.diff; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Data +public class DatabaseDocDiff { + + private Integer id; + + private Integer projectId; + + private String databaseName; + + private String schemaName; + + private String productName; + + private String productVersion; + + private Long version; + + private List tables = Collections.emptyList(); + + private LocalDateTime updateAt; + + private LocalDateTime createAt; + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/DiffAble.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/DiffAble.java new file mode 100644 index 0000000..4ac9ee3 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/DiffAble.java @@ -0,0 +1,10 @@ +package com.salpa.subject.domain.diff; + +import com.salpa.subject.domain.diff.data.DiffType; + +public interface DiffAble { + + void setDiffType(DiffType diffType); + + void setOriginal(T t); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/DiffTypePredictor.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/DiffTypePredictor.java new file mode 100644 index 0000000..e3306f5 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/DiffTypePredictor.java @@ -0,0 +1,32 @@ +package com.salpa.subject.domain.diff; + + +import com.salpa.subject.domain.data.DatabaseDocumentSimpleResponse; +import com.salpa.subject.domain.diff.data.DiffType; + +import java.util.List; + +public class DiffTypePredictor { + + public static DiffType predict(List result) { + long changedItemSize = result.stream() + .filter(item -> !item.getDiffType().isNone()) + .count(); + long addedItemSize = result.stream() + .filter(item -> !item.getDiffType().isNone()) + .filter(item -> item.getDiffType().isAdded()) + .count(); + long removedItemSize = result.stream() + .filter(item -> !item.getDiffType().isNone()) + .filter(item -> item.getDiffType().isRemoved()) + .count(); + if (changedItemSize > 0 && addedItemSize == changedItemSize) { + return DiffType.ADDED; + } else if (changedItemSize > 0 && removedItemSize == changedItemSize) { + return DiffType.REMOVED; + } else { + return result.stream() + .anyMatch(t -> t.getDiffType() != DiffType.NONE) ? DiffType.MODIFIED : DiffType.NONE; + } + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/Diffs.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/Diffs.java new file mode 100644 index 0000000..6c18e64 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/Diffs.java @@ -0,0 +1,17 @@ +package com.salpa.subject.domain.diff; + +import com.salpa.subject.domain.data.DatabaseMeta; +import com.salpa.subject.domain.diff.data.RootDiff; +import com.salpa.subject.domain.diff.processor.DatabaseDiffProcessor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Diffs { + + private static final DatabaseDiffProcessor databaseDiffProcessor = new DatabaseDiffProcessor(); + + public static RootDiff diff(DatabaseMeta original, DatabaseMeta current) { + return databaseDiffProcessor.process(original, current); + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/DocumentDiffChecker.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/DocumentDiffChecker.java new file mode 100644 index 0000000..46f476e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/DocumentDiffChecker.java @@ -0,0 +1,230 @@ +package com.salpa.subject.domain.diff; + +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +@Slf4j +public class DocumentDiffChecker { + + private static final List IGNORE_FIELDS = List.of( + "id", + "class", + "tableDocumentId", + "databaseDocumentId", + "createAt", + "updateAt", + "diffType", + "original" + ); + + public List diff(List original, List current) { + Map originalTablesMap = toMap(original, TableDocDiff::getName); + Map currentTablesMap = toMap(current, TableDocDiff::getName); + // added items + var added = added(originalTablesMap, currentTablesMap) + .stream() + .map(curr -> { + List columnDiffs = + doDiff(Collections.emptyList(), curr.getColumns(), ColumnDocDiff::getName); + columnDiffs.sort(Comparator.comparingInt(c -> c.getId())); + List indexDiffs = + doDiff(Collections.emptyList(), curr.getIndexes(), IndexDocDiff::getName); + List triggerDiffs = + doDiff(Collections.emptyList(), curr.getTriggers(), TriggerDocDiff::getName); + List fkDiffs = + doDiff(Collections.emptyList(), + curr.getForeignKeys(), + fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); + return TableDocDiff.builder() + .id(curr.getId()) + .diffType(DiffType.ADDED) + .name(curr.getName()) + .comment(curr.getComment()) + .type(curr.getType()) + .createAt(curr.getCreateAt()) + .columns(columnDiffs) + .indexes(indexDiffs) + .triggers(triggerDiffs) + .foreignKeys(fkDiffs) + .build(); + }) + .collect(Collectors.toList()); + // removed items + var removed = removed(originalTablesMap, currentTablesMap) + .stream() + .map(old -> { + List columnDiffs = + doDiff(old.getColumns(), Collections.emptyList(), ColumnDocDiff::getName); + columnDiffs.sort(Comparator.comparingInt(c -> c.getId())); + List indexDiffs = + doDiff(old.getIndexes(), Collections.emptyList(), IndexDocDiff::getName); + List triggerDiffs = + doDiff(old.getTriggers(), Collections.emptyList(), TriggerDocDiff::getName); + List fkDiffs = + doDiff(old.getForeignKeys(), + Collections.emptyList(), + fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); + return TableDocDiff.builder() + .id(old.getId()) + .diffType(DiffType.REMOVED) + .name(old.getName()) + .comment(old.getComment()) + .type(old.getType()) + .createAt(old.getCreateAt()) + .columns(columnDiffs) + .indexes(indexDiffs) + .triggers(triggerDiffs) + .foreignKeys(fkDiffs) + .build(); + }) + .collect(Collectors.toList()); + // unchanged or modified items + List sameOrModified = currentTablesMap.entrySet() + .stream() + .filter(entry -> originalTablesMap.containsKey(entry.getKey())) + .map(entry -> { + String tableName = entry.getKey(); + TableDocDiff currentTable = entry.getValue(); + TableDocDiff originalTable = originalTablesMap.get(tableName); + + List columnDiffs = + doDiff(originalTable.getColumns(), currentTable.getColumns(), ColumnDocDiff::getName); + columnDiffs.sort(Comparator.comparingInt(c -> c.getId())); + List indexDiffs = + doDiff(originalTable.getIndexes(), currentTable.getIndexes(), IndexDocDiff::getName); + List triggerDiffs = + doDiff(originalTable.getTriggers(), currentTable.getTriggers(), TriggerDocDiff::getName); + List fkDiffs = + doDiff(originalTable.getForeignKeys(), + currentTable.getForeignKeys(), + fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); + + BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS); + DiffType diffType = eq.apply(currentTable, originalTable) ? DiffType.NONE : DiffType.MODIFIED; + // workaround for diffType = NONE + if (diffType == DiffType.NONE) { + originalTable = null; + } + return TableDocDiff.builder() + .id(currentTable.getId()) + .diffType(diffType) + .original(originalTable) + .name(currentTable.getName()) + .comment(currentTable.getComment()) + .type(currentTable.getType()) + .createAt(currentTable.getCreateAt()) + .columns(columnDiffs) + .indexes(indexDiffs) + .triggers(triggerDiffs) + .foreignKeys(fkDiffs) + .build(); + }) + .collect(Collectors.toList()); + + List all = new ArrayList<>(16); + all.addAll(sameOrModified); + all.addAll(added); + all.addAll(removed); + return all; + } + + private > List doDiff(List original, List current, Function idMapping) { + Map originalMap = toMap(original, idMapping); + Map currentMap = toMap(current, idMapping); + List added = added(originalMap, currentMap); + List removed = removed(originalMap, currentMap); + List modified = modified(originalMap, currentMap); + List same = same(originalMap, currentMap); + List results = new ArrayList<>(); + results.addAll(same); + results.addAll(added); + results.addAll(modified); + results.addAll(removed); + return results; + } + + private Map toMap(List content, Function idMapping) { + return content + .stream() + .collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> { + log.warn("Duplicate key, origin = {}, current = {}", a, b); + return a; + })); + } + + private > List added(Map originalMapById, + Map currentMapById) { + return currentMapById.entrySet() + .stream() + .filter(entry -> !originalMapById.containsKey(entry.getKey())) + .map(Map.Entry::getValue) + .map(value -> { + value.setDiffType(DiffType.ADDED); + return value; + }) + .collect(Collectors.toList()); + } + + private > List removed(Map originalMapById, + Map currentMapById) { + return originalMapById.entrySet() + .stream() + .filter(entry -> !currentMapById.containsKey(entry.getKey())) + .map(Map.Entry::getValue) + .map(value -> { + value.setDiffType(DiffType.REMOVED); + return value; + }) + .collect(Collectors.toList()); + } + + private > List modified(Map originalMapById, + Map currentMapById) { + BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS); + return modified(originalMapById, currentMapById, eq); + } + + private > List modified(Map originalMapById, + Map currentMapById, + BiFunction sameFunction) { + return currentMapById.entrySet() + .stream() + .filter(entry -> originalMapById.containsKey(entry.getKey())) + .filter(entry -> !sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey()))) + .map(entry -> { + T value = entry.getValue(); + value.setDiffType(DiffType.MODIFIED); + value.setOriginal(originalMapById.get(entry.getKey())); + return value; + }) + .collect(Collectors.toList()); + } + + private > List same(Map originalMapById, + Map currentMapById) { + BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS); + return same(originalMapById, currentMapById, eq); + } + + private > List same(Map originalMapById, + Map currentMapById, + BiFunction sameFunction) { + return currentMapById.entrySet() + .stream() + .filter(entry -> originalMapById.containsKey(entry.getKey())) + .filter(entry -> sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey()))) + .map(entry -> { + T value = entry.getValue(); + value.setDiffType(DiffType.NONE); + return value; + }) + .collect(Collectors.toList()); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/ForeignKeyDocDiff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/ForeignKeyDocDiff.java new file mode 100644 index 0000000..a90328f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/ForeignKeyDocDiff.java @@ -0,0 +1,40 @@ +package com.salpa.subject.domain.diff; + +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class ForeignKeyDocDiff implements DiffAble { + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private Integer keySeq; + + private String fkName; + + private String fkTableName; + + private String fkColumnName; + + private String pkName; + + private String pkTableName; + + private String pkColumnName; + + private String updateRule; + + private String deleteRule; + + private LocalDateTime createAt; + + private DiffType diffType; + + private ForeignKeyDocDiff original; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/IndexDocDiff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/IndexDocDiff.java new file mode 100644 index 0000000..a0f66db --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/IndexDocDiff.java @@ -0,0 +1,29 @@ +package com.salpa.subject.domain.diff; + +import com.alibaba.fastjson2.JSON; +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class IndexDocDiff implements DiffAble { + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String name; + + private Boolean isUnique; + + private String columnNameArray; + + private LocalDateTime createAt; + + private DiffType diffType; + + private IndexDocDiff original; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/TableDocDiff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/TableDocDiff.java new file mode 100644 index 0000000..ed388f2 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/TableDocDiff.java @@ -0,0 +1,41 @@ +package com.salpa.subject.domain.diff; + +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Data +@Builder +public class TableDocDiff implements DiffAble { + + private Integer id; + + private String name; + + private String type; + + private String comment; + + @Builder.Default + private List columns = Collections.emptyList(); + + @Builder.Default + private List indexes = Collections.emptyList(); + + @Builder.Default + private List triggers = Collections.emptyList(); + + @Builder.Default + private List foreignKeys = Collections.emptyList(); + + private LocalDateTime createAt; + + private DiffType diffType; + + private TableDocDiff original; + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/TriggerDocDiff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/TriggerDocDiff.java new file mode 100644 index 0000000..b62c557 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/TriggerDocDiff.java @@ -0,0 +1,32 @@ +package com.salpa.subject.domain.diff; + +import com.salpa.subject.domain.diff.data.DiffType; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class TriggerDocDiff implements DiffAble { + + private Integer id; + + private String name; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String timing; + + private String manipulation; + + private String statement; + + private String triggerCreateAt; + + private LocalDateTime createAt; + + private DiffType diffType; + + private TriggerDocDiff original; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/Diff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/Diff.java new file mode 100644 index 0000000..c349a20 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/Diff.java @@ -0,0 +1,11 @@ +package com.salpa.subject.domain.diff.data; + +public interface Diff { + + DiffType getDiffType(); + + Object getOriginal(); + + Object getCurrent(); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/DiffType.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/DiffType.java new file mode 100644 index 0000000..75d6083 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/DiffType.java @@ -0,0 +1,21 @@ +package com.salpa.subject.domain.diff.data; + +public enum DiffType { + NONE, ADDED, REMOVED, MODIFIED; + + public static boolean isModified(DiffType type) { + return type != null && type != NONE; + } + + public boolean isAdded() { + return this == ADDED; + } + + public boolean isRemoved() { + return this == REMOVED; + } + + public boolean isNone() { + return this == NONE; + } +} \ No newline at end of file diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/FieldDiff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/FieldDiff.java new file mode 100644 index 0000000..2713b94 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/FieldDiff.java @@ -0,0 +1,24 @@ +package com.salpa.subject.domain.diff.data; + +import lombok.Builder; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +public class FieldDiff implements Diff { + + private String fieldName; + + private DiffType diffType; + + private Object original; + + private Object current; + + @Builder.Default + private List fields = new ArrayList<>(); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/RootDiff.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/RootDiff.java new file mode 100644 index 0000000..ad97e2e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/data/RootDiff.java @@ -0,0 +1,15 @@ +package com.salpa.subject.domain.diff.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class RootDiff { + + private DiffType diffType; + + private List fields = new ArrayList<>(); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/ColumnDiffProcessor.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/ColumnDiffProcessor.java new file mode 100644 index 0000000..4d0cc02 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/ColumnDiffProcessor.java @@ -0,0 +1,15 @@ +package com.salpa.subject.domain.diff.processor; + + +import com.salpa.subject.domain.data.ColumnMeta; +import com.salpa.subject.domain.diff.data.FieldDiff; + +import java.util.List; + +public class ColumnDiffProcessor implements DiffProcessor { + + @Override + public FieldDiff process(String fieldName, List original, List current) { + return diffTableField(original, current, fieldName, ColumnMeta::getName); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/DatabaseDiffProcessor.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/DatabaseDiffProcessor.java new file mode 100644 index 0000000..ab5f105 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/DatabaseDiffProcessor.java @@ -0,0 +1,81 @@ +package com.salpa.subject.domain.diff.processor; + +import com.salpa.subject.domain.data.DatabaseMeta; +import com.salpa.subject.domain.diff.data.DiffType; +import com.salpa.subject.domain.diff.data.FieldDiff; +import com.salpa.subject.domain.diff.data.RootDiff; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@Slf4j +public class DatabaseDiffProcessor { + + private final TableDiffProcessor tableDiffProcessor = new TableDiffProcessor(); + + private static final DatabaseMeta EMPTY = DatabaseMeta.builder().build(); + + public RootDiff process(DatabaseMeta original, DatabaseMeta current) { + DiffType diffType = null; + if (original == null && current != null) { + diffType = DiffType.ADDED; + } + if (original != null && current == null) { + diffType = DiffType.REMOVED; + } + List fields = diffDatabaseFields( + Objects.requireNonNullElse(original, EMPTY), + Objects.requireNonNullElse(current, EMPTY) + ); + boolean isModified = fields.stream().anyMatch(f -> DiffType.isModified(f.getDiffType())); + if (diffType == null) { + diffType = isModified ? DiffType.MODIFIED : DiffType.NONE; + } + RootDiff diff = new RootDiff(); + diff.setFields(fields); + diff.setDiffType(diffType); + return diff; + } + + private List diffDatabaseFields(DatabaseMeta original, DatabaseMeta current) { + Class clazz = DatabaseMeta.class; + Field[] fields = clazz.getDeclaredFields(); + List diffs = new ArrayList<>(32); + // ignore tables diff + Arrays.stream(fields) + .filter(field -> !Objects.equals(field.getName(), "tables")) + .forEach(field -> { + try { + field.setAccessible(true); + Object originalValue = original == null ? null : field.get(original); + Object currentValue = current == null ? null : field.get(current); + if (!Objects.equals(originalValue, currentValue)) { + DiffType diffType; + if (originalValue == null) { + diffType = DiffType.ADDED; + } else if (currentValue == null) { + diffType = DiffType.REMOVED; + } else { + diffType = DiffType.MODIFIED; + } + diffs.add(FieldDiff.builder() + .diffType(diffType) + .fieldName(field.getName()) + .original(originalValue) + .current(currentValue) + .build()); + } + } catch (IllegalAccessException e) { + log.error("diff field failed", e); + } + }); + + FieldDiff tablesField = tableDiffProcessor.process("tables", original.getTables(), current.getTables()); + diffs.add(tablesField); + return diffs; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/DiffProcessor.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/DiffProcessor.java new file mode 100644 index 0000000..08e6439 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/DiffProcessor.java @@ -0,0 +1,101 @@ +package com.salpa.subject.domain.diff.processor; + +import com.salpa.subject.domain.diff.data.DiffType; +import com.salpa.subject.domain.diff.data.FieldDiff; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +public interface DiffProcessor { + + Logger log = LoggerFactory.getLogger(DiffProcessor.class); + + FieldDiff process(String fieldName, List original, List current); + + default FieldDiff diffTableField(List original, + List current, + String fieldName, + Function identity) { + Map originalMap = toMap(original, identity); + Map currentMap = toMap(current, identity); + List columnFieldDiffs = new ArrayList<>(32); + // removed + List removedFields = originalRemovedField(originalMap, currentMap); + columnFieldDiffs.addAll(removedFields); + // added + List addedFields = currentAddedField(originalMap, currentMap); + columnFieldDiffs.addAll(addedFields); + // modified + List modifiedFields = modifiedField(originalMap, currentMap); + columnFieldDiffs.addAll(modifiedFields); + return FieldDiff.builder() + .fieldName(fieldName) + .diffType(columnFieldDiffs.isEmpty() ? DiffType.NONE : DiffType.MODIFIED) + .fields(columnFieldDiffs) + .build(); + + } + + default Map toMap(List content, Function idMapping) { + return content + .stream() + .collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> { + log.warn("Duplicate key, origin = {}, current = {}", a, b); + return a; + })); + } + + default List originalRemovedField(Map originalMapById, + Map currentMapById) { + return originalMapById.entrySet() + .stream() + .filter(entry -> !currentMapById.containsKey(entry.getKey())) + .map(entry -> FieldDiff.builder() + .fieldName(entry.getKey()) + .original(entry.getValue()) + .diffType(DiffType.REMOVED) + .build()) + .collect(Collectors.toList()); + } + + default List currentAddedField(Map originalMapById, + Map currentMapById) { + return currentMapById.entrySet() + .stream() + .filter(entry -> !originalMapById.containsKey(entry.getKey())) + .map(entry -> FieldDiff.builder() + .fieldName(entry.getKey()) + .current(entry.getValue()) + .diffType(DiffType.ADDED) + .build()) + .collect(Collectors.toList()); + } + + default List modifiedField(Map original, + Map current) { + List diff = new ArrayList<>(); + original.entrySet() + .stream() + .filter(entry -> current.containsKey(entry.getKey())) + .forEach(entry -> { + T originalValue = entry.getValue(); + T currentValue = current.get(entry.getKey()); + if (!Objects.equals(originalValue, currentValue)) { + FieldDiff fieldDiff = FieldDiff.builder() + .fieldName(entry.getKey()) + .original(originalValue) + .current(currentValue) + .diffType(DiffType.MODIFIED) + .build(); + diff.add(fieldDiff); + } + }); + return diff; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/ForeignKeyDiffProcessor.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/ForeignKeyDiffProcessor.java new file mode 100644 index 0000000..f2bcda8 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/ForeignKeyDiffProcessor.java @@ -0,0 +1,19 @@ +package com.salpa.subject.domain.diff.processor; + + +import com.salpa.subject.domain.data.ForeignKeyMeta; +import com.salpa.subject.domain.diff.data.FieldDiff; + +import java.util.List; + +public class ForeignKeyDiffProcessor implements DiffProcessor { + + @Override + public FieldDiff process(String fieldName, List original, List current) { + return diffTableField( + original, + current, + "foreignKeys", + fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/IndexDiffProcessor.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/IndexDiffProcessor.java new file mode 100644 index 0000000..fe21238 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/IndexDiffProcessor.java @@ -0,0 +1,15 @@ +package com.salpa.subject.domain.diff.processor; + + +import com.salpa.subject.domain.data.IndexMeta; +import com.salpa.subject.domain.diff.data.FieldDiff; + +import java.util.List; + +public class IndexDiffProcessor implements DiffProcessor { + + @Override + public FieldDiff process(String fieldName, List original, List current) { + return diffTableField(original, current, fieldName, IndexMeta::getName); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/TableDiffProcessor.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/TableDiffProcessor.java new file mode 100644 index 0000000..8b5d825 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/TableDiffProcessor.java @@ -0,0 +1,158 @@ +package com.salpa.subject.domain.diff.processor; + +import com.salpa.subject.domain.data.TableMeta; +import com.salpa.subject.domain.diff.data.DiffType; +import com.salpa.subject.domain.diff.data.FieldDiff; +import lombok.extern.slf4j.Slf4j; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +public class TableDiffProcessor implements DiffProcessor { + + private final IndexDiffProcessor indexDiffProcessor = new IndexDiffProcessor(); + + private final ColumnDiffProcessor columnDiffProcessor = new ColumnDiffProcessor(); + + private final TriggerDiffProcessor triggerDiffProcessor = new TriggerDiffProcessor(); + + private final ForeignKeyDiffProcessor foreignKeyDiffProcessor = new ForeignKeyDiffProcessor(); + + private static final TableMeta EMPTY = new TableMeta(); + + @Override + public FieldDiff process(String fieldName, List original, List current) { + // diff tables field + Map originalMap = toMap(original, TableMeta::getName); + Map currentMap = toMap(current, TableMeta::getName); + List tables = new ArrayList<>(); + List added = added(originalMap, currentMap); + List removed = removed(originalMap, currentMap); + // added + List addedFields = added.stream() + .map(table -> diffTableField(EMPTY, table)) + .collect(Collectors.toList()); + tables.addAll(addedFields); + // removed + List removedFields = removed.stream() + .map(table -> diffTableField(table, EMPTY)) + .collect(Collectors.toList()); + tables.addAll(removedFields); + // modified + List modified = originalMap.entrySet() + .stream() + .filter(entry -> currentMap.containsKey(entry.getKey())) + .filter(entry -> !Objects.equals(entry.getValue(), currentMap.get(entry.getKey()))) + .map(entry -> { + TableMeta originalValue = entry.getValue(); + TableMeta currentValue = currentMap.get(entry.getKey()); + return diffTableField(originalValue, currentValue); + }) + .collect(Collectors.toList()); + tables.addAll(modified); + DiffType tablesDiffType; + if (!modified.isEmpty()) { + tablesDiffType = DiffType.MODIFIED; + } else if (!addedFields.isEmpty()) { + tablesDiffType = DiffType.ADDED; + } else if (!removedFields.isEmpty()) { + tablesDiffType = DiffType.REMOVED; + } else { + tablesDiffType = DiffType.NONE; + } + FieldDiff tablesField = FieldDiff.builder() + .diffType(tablesDiffType) + .fieldName(fieldName) + .fields(tables) + .build(); + return tablesField; + } + + private List added(Map originalMap, + Map currentMap) { + return currentMap.entrySet() + .stream() + .filter(entry -> !originalMap.containsKey(entry.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + } + + private List removed(Map originalMap, + Map currentMap) { + return originalMap.entrySet() + .stream() + .filter(entry -> !currentMap.containsKey(entry.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + } + + private FieldDiff diffTableField(TableMeta original, TableMeta current) { + FieldDiff columns = + columnDiffProcessor.process("columns", original.getColumns(), current.getColumns()); + FieldDiff indexes = + indexDiffProcessor.process("indexes", original.getIndexes(), current.getIndexes()); + FieldDiff triggers = + triggerDiffProcessor.process("triggers", original.getTriggers(), current.getTriggers()); + FieldDiff foreignKeys = + foreignKeyDiffProcessor.process("foreignKeys", original.getForeignKeys(), current.getForeignKeys()); + List otherFields = fields(original, current); + + List fields = new ArrayList<>(); + fields.add(columns); + fields.add(indexes); + fields.add(foreignKeys); + fields.add(triggers); + fields.addAll(otherFields); + DiffType diffType; + if (original == EMPTY) { + diffType = DiffType.ADDED; + } else if (current == EMPTY) { + diffType = DiffType.REMOVED; + } else { + diffType = DiffType.MODIFIED; + } + return FieldDiff.builder() + .diffType(diffType) + .fieldName(original == EMPTY ? current.getName() : original.getName()) + .original(current == EMPTY ? original : null) + .current(original == EMPTY ? current : null) + .fields(fields) + .build(); + } + + private List fields(TableMeta original, TableMeta current) { + List fields = new ArrayList<>(); + // ignore tables diff + Class clazz = TableMeta.class; + List ignoredFields = List.of("columns", "indexes", "triggers", "foreignKeys"); + Arrays.stream(clazz.getDeclaredFields()) + .filter(field -> !ignoredFields.contains(field.getName())) + .forEach(field -> { + try { + field.setAccessible(true); + Object originalValue = original == null ? null : field.get(original); + Object currentValue = current == null ? null : field.get(current); + if (!Objects.equals(originalValue, currentValue)) { + DiffType diffType; + if (originalValue == null) { + diffType = DiffType.ADDED; + } else if (currentValue == null) { + diffType = DiffType.REMOVED; + } else { + diffType = DiffType.MODIFIED; + } + fields.add(FieldDiff.builder() + .diffType(diffType) + .fieldName(field.getName()) + .original(originalValue) + .current(currentValue) + .build()); + } + } catch (IllegalAccessException e) { + log.error("diff field failed", e); + } + }); + return fields; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/TriggerDiffProcessor.java b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/TriggerDiffProcessor.java new file mode 100644 index 0000000..55ea927 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/diff/processor/TriggerDiffProcessor.java @@ -0,0 +1,15 @@ +package com.salpa.subject.domain.diff.processor; + + +import com.salpa.subject.domain.data.TriggerMeta; +import com.salpa.subject.domain.diff.data.FieldDiff; + +import java.util.List; + +public class TriggerDiffProcessor implements DiffProcessor { + + @Override + public FieldDiff process(String fieldName, List original, List current) { + return diffTableField(original, current, fieldName, TriggerMeta::getName); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/error/DomainErrors.java b/salpa-system/src/main/java/com/salpa/subject/domain/error/DomainErrors.java new file mode 100644 index 0000000..45ff39b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/error/DomainErrors.java @@ -0,0 +1,72 @@ +package com.salpa.subject.domain.error; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum DomainErrors implements MetaopsErrors { + + REFRESH_TOKEN_EXPIRED("X_0001", "refresh token expired"), + INVALID_REFRESH_TOKEN_OPERATION("X_0002", "invalid refresh token operation"), + NETWORK_ERROR("X_0003", "网络似乎不稳定,请稍后再试"), + INVALID_ACCESS_TOKEN("X_0004", "无效的 access token"), + + NOT_SUPPORT_DATABASE_TYPE("A_10000", "不支持的数据库类型, 请检查项目配置"), + PROJECT_NOT_FOUND("A_10001", "项目不存在"), + DATABASE_META_NOT_FOUND("A_10002", "获取数据库信息失败"), + CONNECT_DATABASE_FAILED("A_10003", "连接数据库失败,请检查连接配置"), + GROUP_OWNER_MUST_NOT_BE_EMPTY("A_10004", "请至少指定一个分组组长"), + PASSWORD_MUST_NOT_BE_BLANK("A_10005", "密码不能为空"), + USERNAME_OR_EMAIL_DUPLICATE("A_10006", "用户名或邮箱已存在"), + USER_ROLE_DUPLICATE("A_10007", "用户角色已存在"), + PROJECT_NAME_DUPLICATE("A_10008", "项目名称已被占用"), + CANNOT_UPDATE_SELF_ROLE("A_10009", "无法对自己执行角色变更的操作"), + UPDATE_PASSWORD_CONFIRM_FAILED("A_10010", "两次密码输入不一致"), + ORIGIN_PASSWORD_NOT_CORRECT("A_10011", "原密码不正确"), + INVALID_CRON_EXPRESSION("A_10012", "不合法的 cron 表达式"), + REGISTRATION_ID_DUPLICATE("A_10013", "应用注册 ID 不能重复"), + REGISTRATION_ID_NOT_FOUND("A_10014", "应用 ID 不存在"), + MISS_REQUIRED_PARAMETERS("A_10015", "缺少必填参数"), + DATABASE_TYPE_NAME_DUPLICATE("A_10016", "数据库类型名已存在"), + MUST_NOT_MODIFY_SYSTEM_DEFAULT_DATABASE_TYPE("A_10017", "禁止修改系统默认数据库类型"), + DOWNLOAD_DRIVER_ERROR("A_10018", "驱动下载失败"), + INVALID_DATABASE_TYPE_URL_PATTERN("A_10019", "不合法的 url pattern"), + DOCUMENT_VERSION_IS_INVALID("A_10020", "文档版本不合法"), + CANNOT_UPDATE_SELF_ENABLED_STATUS("A_10021", "无法对自己执行启用禁用操作"), + MOCK_DATA_SCRIPT_MUST_NOT_BE_BLANK("A_10022", "脚本内容不能为空"), + TABLE_META_NOT_FOUND("A_10023", "不存在的数据库表"), + DEPENDENT_COLUMN_NAME_MUST_NOT_BE_BLANK("A_10024", "必须指定依赖的字段"), + DEPENDENT_REF_MUST_NOT_BE_BLANK("A_10025", "请选择关联表和字段"), + MUST_NOT_REF_SELF("A_10026", "不能引用自身"), + CIRCLE_REFERENCE("A_10027", "检查到循环引用"), + DUPLICATE_COLUMN("A_10028", "重复的列"), + INVALID_MOCK_DATA_SCRIPT("A_10029", "不合法的表达式"), + CANNOT_DELETE_SELF("A_10030", "无法对自己执行删除账号操作"), + DRIVER_CLASS_NOT_FOUND("A_10031", "获取驱动类名失败"), + DATABASE_DOCUMENT_DUPLICATE_KEY("A_10032", "文档版本重复"), + UPLOAD_DRIVER_FILE_ERROR("A_10033", "上传失败,请检查后重新上传"), + DRIVER_URL_AND_PATH_MUST_NOT_BE_ALL_BLANK("A_10034", "请填写下载驱动的地址或手动上传驱动文件"), + LOAD_DRIVER_FAILED("A_10045", "驱动加载失败,请检查后重试") + ; + + private final String errCode; + + private final String errMessage; + + public MetaopsException exception() { + return new MetaopsException(this); + } + + public MetaopsException exception(Throwable origin) { + return new MetaopsException(this, origin); + } + + public MetaopsException exception(String message, Throwable origin) { + return new MetaopsException(this, message, origin); + } + + public MetaopsException exception(String s) { + return exception(s, null); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/error/MetaopsErrors.java b/salpa-system/src/main/java/com/salpa/subject/domain/error/MetaopsErrors.java new file mode 100644 index 0000000..b2f0711 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/error/MetaopsErrors.java @@ -0,0 +1,8 @@ +package com.salpa.subject.domain.error; + +public interface MetaopsErrors { + + String getErrCode(); + + String getErrMessage(); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/error/MetaopsException.java b/salpa-system/src/main/java/com/salpa/subject/domain/error/MetaopsException.java new file mode 100644 index 0000000..afbe111 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/error/MetaopsException.java @@ -0,0 +1,56 @@ +package com.salpa.subject.domain.error; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class MetaopsException extends RuntimeException { + + @Getter + private MetaopsErrors errorCodeMessage; + + @Getter + private String errCode; + + @Getter + private String errMessage; + + /** + * @param errorCodeMessage 错误信息 + */ + public MetaopsException(MetaopsErrors errorCodeMessage) { + super(errorCodeMessage.getErrMessage()); + this.errorCodeMessage = errorCodeMessage; + this.errCode = errorCodeMessage.getErrCode(); + this.errMessage = errorCodeMessage.getErrMessage(); + } + + /** + * @param errorCodeMessage 错误信息 + * @param overrideMessage 覆盖 message + */ + public MetaopsException(MetaopsErrors errorCodeMessage, String overrideMessage) { + super(overrideMessage); + this.errorCodeMessage = errorCodeMessage; + this.errCode = errorCodeMessage.getErrCode(); + this.errMessage = overrideMessage; + } + + /** + * @param errorCodeMessage 错误信息 + * @param cause root cause + */ + public MetaopsException(MetaopsErrors errorCodeMessage, Throwable cause) { + super(errorCodeMessage.getErrMessage(), cause); + this.errorCodeMessage = errorCodeMessage; + this.errCode = errorCodeMessage.getErrCode(); + this.errMessage = errorCodeMessage.getErrMessage(); + } + + public MetaopsException(MetaopsErrors errorCodeMessage, String overrideMessage, Throwable cause) { + super(overrideMessage, cause); + this.errorCodeMessage = errorCodeMessage; + this.errCode = errorCodeMessage.getErrCode(); + this.errMessage = overrideMessage; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/generator/DocumentFileGenerator.java b/salpa-system/src/main/java/com/salpa/subject/domain/generator/DocumentFileGenerator.java new file mode 100644 index 0000000..bfcdd6c --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/generator/DocumentFileGenerator.java @@ -0,0 +1,29 @@ +package com.salpa.subject.domain.generator; + +import com.salpa.subject.domain.data.DatabaseDocumentResponse; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import org.springframework.stereotype.Component; + +import java.io.OutputStream; + +@Component +public interface DocumentFileGenerator { + + boolean support(DocumentFileType type); + + void generate(DocumentFileGenerateContext context, OutputStream outputStream); + + @Getter + @Builder + class DocumentFileGenerateContext { + + @NonNull + private DocumentFileType documentFileType; + + @NonNull + private DatabaseDocumentResponse databaseDocument; + + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/generator/DocumentFileType.java b/salpa-system/src/main/java/com/salpa/subject/domain/generator/DocumentFileType.java new file mode 100644 index 0000000..3b8b4a4 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/generator/DocumentFileType.java @@ -0,0 +1,23 @@ +package com.salpa.subject.domain.generator; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum DocumentFileType { + + MARKDOWN("md", "Markdown"), + + PLANT_UML_ER_SVG("svg", "UML SVG"), + + PLANT_UML_ER_PNG("png", "UML PNG"), + + WORD("docx", "Word"), + ; + + private String fileExtension; + + private String name; + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/generator/MarkdownDocumentFileGenerator.java b/salpa-system/src/main/java/com/salpa/subject/domain/generator/MarkdownDocumentFileGenerator.java new file mode 100644 index 0000000..bcf7122 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/generator/MarkdownDocumentFileGenerator.java @@ -0,0 +1,243 @@ +package com.salpa.subject.domain.generator; + +import com.salpa.common.exception.SystemException; +import com.salpa.common.utils.StringUtils; +import com.salpa.subject.domain.data.DatabaseDocumentResponse; +import com.salpa.subject.domain.data.DocumentTemplatePropertiesResponse; +import com.salpa.subject.domain.data.TableDocumentResponse; +import com.salpa.subject.domain.render.markdown.MarkdownBuilder; +import com.salpa.subject.service.DocumentTemplateService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +@Slf4j +@RequiredArgsConstructor +public class MarkdownDocumentFileGenerator implements DocumentFileGenerator { + + @Autowired(required=false) + private DocumentTemplateService documentTemplateService; + + @Override + public boolean support(DocumentFileType type) { + return type == DocumentFileType.MARKDOWN; + } + + @Override + public void generate(DocumentFileGenerateContext context, OutputStream outputStream) { + DocumentTemplatePropertiesResponse templateProperties = documentTemplateService.getAllProperties(); + String fileName = context.getDatabaseDocument().getDatabaseName() + "-" + UUID.randomUUID().toString(); + String data = markdownData(context, templateProperties); + Path tempFile = null; + try { + tempFile = Files.createTempFile(fileName, ".md"); + Path path = Files.writeString(tempFile, data, StandardCharsets.UTF_8); + byte[] bytes = Files.readAllBytes(path); + outputStream.write(bytes); + } catch (IOException e) { + if (tempFile != null) { + try { + Files.deleteIfExists(tempFile); + } catch (IOException ex) { + log.warn("delete temp file error", ex); + } + } + throw new SystemException("System error"); + } + } + + private String markdownData(DocumentFileGenerateContext context, + DocumentTemplatePropertiesResponse properties) { + DatabaseDocumentResponse doc = context.getDatabaseDocument(); + MarkdownBuilder builder = MarkdownBuilder.builder(); + builder.primaryTitle(doc.getDatabaseName()); + // overview + overviewBuild(builder, doc); + // field map by table name + Map columnTitleMap = properties.getColumnFieldNameProperties() + .stream() + .collect(Collectors.toMap(d -> d.getKey(), + d -> Objects.requireNonNullElse(d.getValue(), d.getDefaultValue()))); + Map indexTitleMap = properties.getIndexFieldNameProperties() + .stream() + .collect(Collectors.toMap(d -> d.getKey(), + d -> Objects.requireNonNullElse(d.getValue(), d.getDefaultValue()))); + Map triggerTitleMap = properties.getTriggerFieldNameProperties() + .stream() + .collect(Collectors.toMap(d -> d.getKey(), + d -> Objects.requireNonNullElse(d.getValue(), d.getDefaultValue()))); + Map foreignKeyTitleMap = properties.getForeignKeyFieldNameProperties() + .stream() + .collect(Collectors.toMap(d -> d.getKey(), + d -> Objects.requireNonNullElse(d.getValue(), d.getDefaultValue()))); + // table document build + doc.getTables().forEach(table -> { + if (StringUtils.isNotBlank(table.getComment())) { + builder.secondTitle(table.getName() + " /\\*" + table.getComment() + "\\*/"); + } else { + builder.secondTitle(table.getName()); + } + columnBuild(builder, table, columnTitleMap); + indexBuild(builder, table, indexTitleMap); + foreignKeyBuild(builder, table, foreignKeyTitleMap); + triggerBuild(builder, table, triggerTitleMap); + }); + return builder.build(); + } + + private void overviewBuild(MarkdownBuilder builder, DatabaseDocumentResponse doc) { + builder.secondTitle("overview"); + List> overviewContent = new ArrayList<>(); + for (int i = 0; i < doc.getTables().size(); i++) { + TableDocumentResponse table = doc.getTables().get(i); + List row = List.of((i + 1) + "", + Objects.requireNonNullElse(table.getName(), ""), + Objects.requireNonNullElse(table.getType(), ""), + Objects.requireNonNullElse(table.getComment(), "")); + overviewContent.add(row); + } + builder.table(List.of("", "表名", "类型", "备注"), overviewContent); + } + + private void columnBuild(MarkdownBuilder builder, + TableDocumentResponse table, + Map titleMap) { + Function + columnDefaultValueMapping = column -> { + if (Objects.equals(column.getNullable(), "YES")) { + return Objects.requireNonNullElse(column.getDefaultValue(), "null"); + } else { + return Objects.requireNonNullElse(column.getDefaultValue(), ""); + } + }; + builder.thirdTitle("Columns"); + List> columnContent = new ArrayList<>(); + for (int i = 0; i < table.getColumns().size(); i++) { + var column = table.getColumns().get(i); + String type; + if (column.getDecimalDigits() == null + || Objects.requireNonNullElse(column.getDecimalDigits(), 0) == 0) { + type = column.getType() + "(" + column.getSize() + ")"; + } else { + type = column.getType() + "(" + column.getSize() + "," + column.getDecimalDigits() + ")"; + } + columnContent.add(List.of((i + 1) + "", + column.getName(), + type, + column.getIsPrimaryKey() ? "YES" : "NO", + column.getNullable(), + column.getAutoIncrement(), + columnDefaultValueMapping.apply(column), + Objects.requireNonNullElse(column.getComment(), ""))); + } + builder.table( + List.of( + "", + titleMap.getOrDefault("name", "name"), + titleMap.getOrDefault("type", "type"), + titleMap.getOrDefault("isPrimaryKey", "isPrimaryKey"), + titleMap.getOrDefault("nullable", "nullable"), + titleMap.getOrDefault("autoIncrement", "autoIncrement"), + titleMap.getOrDefault("defaultValue", "defaultValue"), + titleMap.getOrDefault("comment", "comment") + ), + columnContent + ); + } + + private void indexBuild(MarkdownBuilder builder, + TableDocumentResponse table, + Map titleMap) { + builder.thirdTitle("Indexes"); + List> indexContent = new ArrayList<>(); + for (int i = 0; i < table.getIndexes().size(); i++) { + var index = table.getIndexes().get(i); + String columnNames = String.join(", ", index.getColumnNames()); + String isUnique = index.getIsUnique() ? "YES" : "NO"; + indexContent.add(List.of((i + 1) + "", index.getName(), isUnique, columnNames)); + } + builder.table( + List.of( + "", + titleMap.getOrDefault("name", "name"), + titleMap.getOrDefault("isUnique", "isUnique"), + titleMap.getOrDefault("columnNames", "columnNames") + ), + indexContent + ); + } + + private void foreignKeyBuild(MarkdownBuilder builder, + TableDocumentResponse table, + Map titleMap) { + if (!table.getForeignKeys().isEmpty()) { + List> foreignKeys = new ArrayList<>(); + builder.thirdTitle("Foreign Keys"); + for (int i = 0; i < table.getForeignKeys().size(); i++) { + TableDocumentResponse.ForeignKeyDocumentResponse fk = table.getForeignKeys().get(i); + List item = List.of( + (i + 1) + "", + Objects.requireNonNullElse(fk.getFkName(), ""), fk.getFkColumnName(), + Objects.requireNonNullElse(fk.getPkName(), ""), fk.getPkTableName(), + fk.getPkColumnName(), + fk.getUpdateRule(), fk.getDeleteRule() + ); + foreignKeys.add(item); + } + builder.table( + List.of( + "", + titleMap.getOrDefault("fkName", "fkName"), + titleMap.getOrDefault("fkColumnName", "fkColumnName"), + titleMap.getOrDefault("pkName", "pkName"), + titleMap.getOrDefault("pkTableName", "pkTableName"), + titleMap.getOrDefault("pkColumnName", "pkColumnName"), + titleMap.getOrDefault("updateRule", "updateRule"), + titleMap.getOrDefault("deleteRule", "deleteRule") + ), + foreignKeys + ); + } + } + + private void triggerBuild(MarkdownBuilder builder, + TableDocumentResponse table, + Map titleMap) { + if (!table.getTriggers().isEmpty()) { + List> triggerContent = new ArrayList<>(); + for (int i = 0; i < table.getTriggers().size(); i++) { + var trigger = table.getTriggers().get(i); + triggerContent.add( + List.of( + (i + 1) + "", + Objects.requireNonNullElse(trigger.getName(), ""), + Objects.requireNonNullElse(trigger.getTiming(), ""), + Objects.requireNonNullElse(trigger.getManipulation(), ""), + Objects.requireNonNullElse(trigger.getStatement(), "") + )); + } + builder.thirdTitle("Triggers"); + builder.table( + List.of( + "", + titleMap.getOrDefault("name", "name"), + titleMap.getOrDefault("timing", "timing"), + titleMap.getOrDefault("manipulation", "manipulation"), + titleMap.getOrDefault("statement", "statement") + ), + triggerContent + ); + } + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/generator/WordFileGenerator.java b/salpa-system/src/main/java/com/salpa/subject/domain/generator/WordFileGenerator.java new file mode 100644 index 0000000..ccd5bff --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/generator/WordFileGenerator.java @@ -0,0 +1,81 @@ +package com.salpa.subject.domain.generator; + +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.config.Configure; +import com.deepoove.poi.plugin.table.HackLoopTableRenderPolicy; +import com.salpa.common.exception.SystemException; +import com.salpa.subject.domain.data.TableDocumentResponse; +import com.salpa.subject.domain.vo.ColumnVo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +@Slf4j +@RequiredArgsConstructor +public class WordFileGenerator implements DocumentFileGenerator { + + @Override + public boolean support(DocumentFileType type) { + return type == DocumentFileType.WORD; + } + + @Override + public void generate(DocumentFileGenerateContext context, OutputStream outputStream) { + try { + Map data = new HashMap<>(); + data.put("databaseName",context.getDatabaseDocument().getDatabaseName()); + List> tList=new ArrayList<>(); + List> tables = new ArrayList<>(); + List tables1 = context.getDatabaseDocument().getTables(); + for (TableDocumentResponse tableDocumentResponse : tables1) { + Map table = new HashMap<>(); + table.put("tableName",tableDocumentResponse.getName()); + table.put("tableType",tableDocumentResponse.getType()); + table.put("tableComment",tableDocumentResponse.getComment()); + tables.add(table); + List columns1 = tableDocumentResponse.getColumns(); + List columns = new ArrayList<>(); + Map tMap = new HashMap<>(); + for (TableDocumentResponse.ColumnDocumentResponse column : columns1) { + ColumnVo columnVo = new ColumnVo(); + columnVo.setName(column.getName()); + columnVo.setType(column.getType() + "(" + column.getSize() + ")"); + if (column.getIsPrimaryKey()) { + columnVo.setPrimaryKey("YES"); + } else { + columnVo.setPrimaryKey("NO"); + } + columnVo.setNullable(column.getNullable()); + columnVo.setAutoIncrement(column.getAutoIncrement()); + columnVo.setDefaultValue(column.getDefaultValue()); + columnVo.setComment(column.getComment()); + columns.add(columnVo); + } + tMap.put("tableName",tableDocumentResponse.getName()); + tMap.put("columns",columns); + tList.add(tMap); + } + data.put("tables",tables); + data.put("tableColumns",tList); + InputStream inputStream = new ClassPathResource("word.docx").getInputStream(); + HackLoopTableRenderPolicy policy = new HackLoopTableRenderPolicy(); + Configure config = Configure.newBuilder().bind("tables", policy).bind("columns",policy).build(); + XWPFTemplate template = XWPFTemplate.compile(inputStream,config).render(data); + template.write(outputStream); + } catch (IOException e) { + throw new SystemException("System error"); + } + } + + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/BasePlantUmlFileGenerator.java b/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/BasePlantUmlFileGenerator.java new file mode 100644 index 0000000..c3cb207 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/BasePlantUmlFileGenerator.java @@ -0,0 +1,153 @@ +package com.salpa.subject.domain.generator.plantuml; + +import com.salpa.subject.domain.data.DatabaseDocumentResponse; +import com.salpa.subject.domain.data.TableDocumentResponse; +import com.salpa.subject.domain.generator.DocumentFileGenerator; +import com.salpa.common.exception.SystemException; +import lombok.extern.slf4j.Slf4j; +import net.sourceforge.plantuml.FileFormatOption; +import net.sourceforge.plantuml.SourceStringReader; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +public abstract class BasePlantUmlFileGenerator implements DocumentFileGenerator { + + public static final String LINE = "\r\n"; + + @Override + public void generate(DocumentFileGenerateContext context, OutputStream outputStream) { + String dsl = new ErDsl(context).toDsl(); + try { + new SourceStringReader(dsl).outputImage(outputStream, fileFormatOption()); + } catch (IOException e) { + log.error("export plantuml error", e); + throw new SystemException("System error"); + } + } + + protected abstract FileFormatOption fileFormatOption(); + + public class ErDsl { + + private final DocumentFileGenerateContext context; + + private Set foreignKeyRelations = new HashSet<>(16); + + private Set tables = new HashSet<>(16); + + public ErDsl(DocumentFileGenerateContext context) { + this.context = context; + Set tables = context.getDatabaseDocument() + .getTables() + .stream() + .map(TableDocumentResponse::getName) + .collect(Collectors.toSet()); + this.tables = tables; + } + + public String toDsl() { + DatabaseDocumentResponse databaseDocument = context.getDatabaseDocument(); + StringBuilder dslBuilder = new StringBuilder(1024); + // use smetana engine + dslBuilder.append("@startuml").append(LINE) + .append("!pragma layout smetana").append(LINE); + + // configuration + dslBuilder.append("' hide the spot").append(LINE); + dslBuilder.append("hide circle").append(LINE); + + //分辨率像素 + dslBuilder.append("skinparam dpi 48").append(LINE); + dslBuilder.append("scale 8192 width").append(LINE); + dslBuilder.append("scale 8192 height").append(LINE); + + // entities + String entities = databaseDocument.getTables() + .stream() + .map(table -> toErDsl(table)) + .collect(Collectors.joining(LINE)); + dslBuilder.append(entities); + + // relation + dslBuilder.append(LINE); + String relations = foreignKeyRelations.stream() + .collect(Collectors.joining(LINE)); + dslBuilder.append(relations); + dslBuilder.append(LINE); + + dslBuilder.append("@enduml"); + if (log.isDebugEnabled()) { + log.debug("------------------------------"); + log.debug(dslBuilder.toString()); + log.debug("------------------------------"); + } + return dslBuilder.toString(); + } + + private String toErDsl(TableDocumentResponse table) { + StringBuilder dslBuilder = new StringBuilder(1024); + dslBuilder.append("entity ").append(table.getName()) + .append(" {"); + table.getColumns() + .stream() + .filter(TableDocumentResponse.ColumnDocumentResponse::getIsPrimaryKey) + .forEach(primaryCol -> { + dslBuilder.append(LINE); + dslBuilder.append("*") + .append(primaryCol.getName()) + .append(" : ") + .append(primaryCol.getType()) + .append("(") + .append(primaryCol.getSize()) + .append(")") + .append(" "); + dslBuilder.append(LINE); + }); + table.getColumns() + .stream() + .filter(col -> !col.getIsPrimaryKey()) + .forEach(col -> { + dslBuilder.append(LINE); + if ("NO".equalsIgnoreCase(col.getNullable())) { + dslBuilder.append("*"); + } + dslBuilder.append(col.getName()) + .append(" : ") + .append(col.getType()) + .append("(") + .append(col.getSize()) + .append(")"); + if (col.getComment() != null && !"".equals(col.getComment().trim())) { + dslBuilder.append(" /* ").append(col.getComment()).append(" */"); + } + dslBuilder.append(LINE); + }); + dslBuilder.append("}"); + dslBuilder.append(LINE); + + table.getForeignKeys() + .stream() + .filter(fk -> tables.contains(fk.getFkTableName()) && tables.contains(fk.getPkTableName())) + .forEach(fk -> { + String fkTableName = fk.getFkTableName(); + String fkColumnName = fk.getFkColumnName(); + String pkTableName = fk.getPkTableName(); + String pkColumnName = fk.getPkColumnName(); + StringBuilder relationBuilder = new StringBuilder(); + relationBuilder.append(fkTableName).append("::").append(fkColumnName) + .append(" --> ") + .append(pkTableName).append("::").append(pkColumnName) + .append(" : ") + .append(Objects.requireNonNullElse(fk.getFkName(), "")); + foreignKeyRelations.add(relationBuilder.toString()); + }); + return dslBuilder.toString(); + } + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/PlantUmlErSvgFileGenerator.java b/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/PlantUmlErSvgFileGenerator.java new file mode 100644 index 0000000..e5d404b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/PlantUmlErSvgFileGenerator.java @@ -0,0 +1,21 @@ +package com.salpa.subject.domain.generator.plantuml; + +import lombok.extern.slf4j.Slf4j; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FileFormatOption; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class PlantUmlErSvgFileGenerator extends BasePlantUmlFileGenerator { + + @Override + public boolean support(com.salpa.subject.domain.generator.DocumentFileType type) { + return type == com.salpa.subject.domain.generator.DocumentFileType.PLANT_UML_ER_SVG; + } + + @Override + protected FileFormatOption fileFormatOption() { + return new FileFormatOption(FileFormat.SVG); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/PlantUmlPngFileGenerator.java b/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/PlantUmlPngFileGenerator.java new file mode 100644 index 0000000..2a82a14 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/generator/plantuml/PlantUmlPngFileGenerator.java @@ -0,0 +1,22 @@ +package com.salpa.subject.domain.generator.plantuml; + +import com.salpa.subject.domain.generator.DocumentFileType; +import lombok.extern.slf4j.Slf4j; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FileFormatOption; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class PlantUmlPngFileGenerator extends BasePlantUmlFileGenerator { + + @Override + public boolean support(DocumentFileType type) { + return type == DocumentFileType.PLANT_UML_ER_PNG; + } + + @Override + protected FileFormatOption fileFormatOption() { + return new FileFormatOption(FileFormat.PNG); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/AbstractSqlTriggerMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/AbstractSqlTriggerMetaProvider.java new file mode 100644 index 0000000..ec0ce10 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/AbstractSqlTriggerMetaProvider.java @@ -0,0 +1,82 @@ +package com.salpa.subject.domain.provider; + +import com.salpa.subject.domain.data.TriggerMeta; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@Slf4j +public abstract class AbstractSqlTriggerMetaProvider implements TriggerMetaProvider { + + @Override + public List selectTriggers(Connection connection, TableCondition condition) { + String sql = sql(condition); + PreparedStatement preparedStatement = null; + try { + preparedStatement = connection.prepareStatement(sql); + ResultSet results = preparedStatement.executeQuery(); + List triggers = new ArrayList<>(); + while (results.next()) { + String name = getTriggerName(results); + String statement = getStatement(results); + String timing = getTiming(results); + String manipulation = getManipulation(results); + String created = getCreateAt(results); + TriggerMeta meta = TriggerMeta.builder() + .name(name) + .manipulation(manipulation) + .timing(timing) + .statement(statement) + .createAt(created) + .build(); + triggers.add(meta); + } + return triggers; + } catch (SQLException e) { + log.warn("get trigger meta failed", e); + return Collections.emptyList(); + } finally { + if (preparedStatement != null) { + try { + preparedStatement.close(); + } catch (SQLException e) { + // ignore + } + } + } + } + + protected abstract String sql(TableCondition condition); + + protected String getTriggerName(ResultSet results) throws SQLException { + return Objects.requireNonNullElse(results.getString("TRIGGER_NAME"), ""); + } + + protected String getStatement(ResultSet results) throws SQLException { + return results.getString("ACTION_STATEMENT"); + } + + protected String getTiming(ResultSet results) throws SQLException { + return results.getString("ACTION_TIMING"); + } + + protected String getManipulation(ResultSet results) throws SQLException { + return results.getString("EVENT_MANIPULATION"); + } + + protected String getCreateAt(ResultSet results) throws SQLException { + String created = results.getString("CREATED"); + if (created == null) { + return "unknown"; + } + return created; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/ColumnMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/ColumnMetaProvider.java new file mode 100644 index 0000000..22e2307 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/ColumnMetaProvider.java @@ -0,0 +1,14 @@ +package com.salpa.subject.domain.provider; + + +import com.salpa.subject.domain.data.ColumnMeta; +import com.salpa.subject.domain.provider.condition.TableCondition; + +import java.sql.Connection; +import java.util.List; + +public interface ColumnMetaProvider { + + List selectColumns(Connection connection, TableCondition condition); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/DatabaseMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/DatabaseMetaProvider.java new file mode 100644 index 0000000..8c35804 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/DatabaseMetaProvider.java @@ -0,0 +1,14 @@ +package com.salpa.subject.domain.provider; + + +import com.salpa.subject.domain.data.DatabaseMeta; +import com.salpa.subject.domain.provider.condition.Condition; + +import java.sql.Connection; +import java.util.Optional; + +public interface DatabaseMetaProvider { + + Optional select(Connection connection, Condition condition); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/ForeignKeyMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/ForeignKeyMetaProvider.java new file mode 100644 index 0000000..a4e07a1 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/ForeignKeyMetaProvider.java @@ -0,0 +1,14 @@ +package com.salpa.subject.domain.provider; + + +import com.salpa.subject.domain.data.ForeignKeyMeta; +import com.salpa.subject.domain.provider.condition.TableCondition; + +import java.sql.Connection; +import java.util.List; + +public interface ForeignKeyMetaProvider { + + List selectForeignKeys(Connection connection, TableCondition condition); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/IndexMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/IndexMetaProvider.java new file mode 100644 index 0000000..fd5aa6b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/IndexMetaProvider.java @@ -0,0 +1,14 @@ +package com.salpa.subject.domain.provider; + + + +import com.salpa.subject.domain.data.IndexMeta; +import com.salpa.subject.domain.provider.condition.TableCondition; + +import java.sql.Connection; +import java.util.List; + +public interface IndexMetaProvider { + + List selectIndexes(Connection connection, TableCondition condition); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/MetaProviders.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/MetaProviders.java new file mode 100644 index 0000000..7c6aa13 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/MetaProviders.java @@ -0,0 +1,130 @@ +package com.salpa.subject.domain.provider; + +import com.salpa.subject.domain.provider.jdbc.*; +import com.salpa.subject.domain.provider.maria.MariaTriggerMetaProvider; +import com.salpa.subject.domain.provider.mysql.MysqlTableTriggerMetaProvider; +import com.salpa.subject.domain.provider.oracle.OracleTriggerMetaProvider; +import com.salpa.subject.domain.provider.postgresql.PostgresqlTriggerMetaProvider; +import com.salpa.subject.domain.provider.sqlserver.SqlServerColumnMetaProvider; +import com.salpa.subject.domain.provider.sqlserver.SqlServerTableMetaProvider; +import com.salpa.subject.domain.provider.sqlserver.SqlServerTriggerMetaProvider; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +@Slf4j +public class MetaProviders { + + public static DatabaseMetaProvider jdbc() { + var columnMetaProvider = new JdbcColumnMetaProvider(); + var foreignKeyMetaProvider = new JdbcForeignKeyMetaProvider(); + var indexMetaProvider = new JdbcIndexMetaProvider(); + var triggerMetaProvider = new JdbcTriggerMetaProvider(); + var tableMetaProvider = new JdbcTableMetaProvider( + columnMetaProvider, + indexMetaProvider, + triggerMetaProvider, + foreignKeyMetaProvider + ); + return new JdbcDatabaseMetaProvider(tableMetaProvider); + } + + public static DatabaseMetaProvider of(Connection connection) { + String url; + try { + DatabaseMetaData metaData = connection.getMetaData(); + url = metaData.getURL(); + } catch (SQLException e) { + log.warn("failed to get connect url, {}, fallback to jdbc provider", e.getMessage()); + return jdbc(); + } + if (url.contains(":sqlserver:")) { + return sqlServer(); + } + if (url.contains(":mysql:")) { + return mysql(); + } + if (url.contains(":postgresql:") || url.contains(":pgsql:")) { + return postgresql(); + } + if (url.contains(":mariadb:")) { + return mariaDB(); + } + if (url.contains(":oracle:")) { + return oracle(); + } + return jdbc(); + } + + private static DatabaseMetaProvider mysql() { + var columnMetaProvider = new JdbcColumnMetaProvider(); + var foreignKeyMetaProvider = new JdbcForeignKeyMetaProvider(); + var indexMetaProvider = new JdbcIndexMetaProvider(); + var triggerMetaProvider = new MysqlTableTriggerMetaProvider(); + var tableMetaProvider = new JdbcTableMetaProvider( + columnMetaProvider, + indexMetaProvider, + triggerMetaProvider, + foreignKeyMetaProvider + ); + return new JdbcDatabaseMetaProvider(tableMetaProvider); + } + + private static DatabaseMetaProvider sqlServer() { + var columnMetaProvider = new SqlServerColumnMetaProvider(); + var foreignKeyMetaProvider = new JdbcForeignKeyMetaProvider(); + var indexMetaProvider = new JdbcIndexMetaProvider(); + var triggerMetaProvider = new SqlServerTriggerMetaProvider(); + var tableMetaProvider = new SqlServerTableMetaProvider( + columnMetaProvider, + indexMetaProvider, + foreignKeyMetaProvider, + triggerMetaProvider + ); + return new JdbcDatabaseMetaProvider(tableMetaProvider); + } + + private static DatabaseMetaProvider postgresql() { + var columnMetaProvider = new JdbcColumnMetaProvider(); + var foreignKeyMetaProvider = new JdbcForeignKeyMetaProvider(); + var indexMetaProvider = new JdbcIndexMetaProvider(); + var triggerMetaProvider = new PostgresqlTriggerMetaProvider(); + var tableMetaProvider = new JdbcTableMetaProvider( + columnMetaProvider, + indexMetaProvider, + triggerMetaProvider, + foreignKeyMetaProvider + ); + return new JdbcDatabaseMetaProvider(tableMetaProvider); + } + + private static DatabaseMetaProvider mariaDB() { + var columnMetaProvider = new JdbcColumnMetaProvider(); + var foreignKeyMetaProvider = new JdbcForeignKeyMetaProvider(); + var indexMetaProvider = new JdbcIndexMetaProvider(); + var triggerMetaProvider = new MariaTriggerMetaProvider(); + var tableMetaProvider = new JdbcTableMetaProvider( + columnMetaProvider, + indexMetaProvider, + triggerMetaProvider, + foreignKeyMetaProvider + ); + return new JdbcDatabaseMetaProvider(tableMetaProvider); + } + + private static DatabaseMetaProvider oracle() { + var columnMetaProvider = new JdbcColumnMetaProvider(); + var foreignKeyMetaProvider = new JdbcForeignKeyMetaProvider(); + var indexMetaProvider = new JdbcIndexMetaProvider(); + var triggerMetaProvider = new OracleTriggerMetaProvider(); + var tableMetaProvider = new JdbcTableMetaProvider( + columnMetaProvider, + indexMetaProvider, + triggerMetaProvider, + foreignKeyMetaProvider + ); + return new JdbcDatabaseMetaProvider(tableMetaProvider); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/TableMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/TableMetaProvider.java new file mode 100644 index 0000000..0cd2617 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/TableMetaProvider.java @@ -0,0 +1,14 @@ +package com.salpa.subject.domain.provider; + + +import com.salpa.subject.domain.data.TableMeta; +import com.salpa.subject.domain.provider.condition.Condition; + +import java.sql.Connection; +import java.util.List; + +public interface TableMetaProvider { + + List selectTables(Connection connection, Condition condition); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/TriggerMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/TriggerMetaProvider.java new file mode 100644 index 0000000..166dd54 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/TriggerMetaProvider.java @@ -0,0 +1,14 @@ +package com.salpa.subject.domain.provider; + + +import com.salpa.subject.domain.data.TriggerMeta; +import com.salpa.subject.domain.provider.condition.TableCondition; + +import java.sql.Connection; +import java.util.List; + +public interface TriggerMetaProvider { + + List selectTriggers(Connection connection, TableCondition condition); + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/condition/Condition.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/condition/Condition.java new file mode 100644 index 0000000..7da5a09 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/condition/Condition.java @@ -0,0 +1,36 @@ +package com.salpa.subject.domain.provider.condition; + +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Pattern; + +@SuperBuilder +@Getter +public class Condition { + + @NonNull + private String databaseName; + + private String schemaName; + + @Builder.Default + @Builder.ObtainVia(method = "ignoreTableNameRegexes") + private Collection ignoreTableNamePatterns = Collections.emptyList(); + + @Builder.Default + @Builder.ObtainVia(method = "ignoreTableColumnNameRegexes") + private Collection ignoreTableColumnNamePatterns = Collections.emptyList(); + + public boolean tableIsIgnored(String tableName) { + return ignoreTableNamePatterns.stream().anyMatch(regex -> regex.matcher(tableName).matches()); + } + + public boolean columnIsIgnored(String column) { + return ignoreTableColumnNamePatterns.stream().anyMatch(regex -> regex.matcher(column).matches()); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/condition/TableCondition.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/condition/TableCondition.java new file mode 100644 index 0000000..bce01d4 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/condition/TableCondition.java @@ -0,0 +1,24 @@ +package com.salpa.subject.domain.provider.condition; + +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TableCondition extends Condition { + + @NonNull + private String tableName; + + public static TableCondition of(Condition condition, String tableName) { + return TableCondition.builder() + .databaseName(condition.getDatabaseName()) + .tableName(tableName) + .schemaName(condition.getSchemaName()) + .ignoreTableNamePatterns(condition.getIgnoreTableNamePatterns()) + .ignoreTableColumnNamePatterns(condition.getIgnoreTableColumnNamePatterns()) + .build(); + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcColumnMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcColumnMetaProvider.java new file mode 100644 index 0000000..c55480f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcColumnMetaProvider.java @@ -0,0 +1,129 @@ +package com.salpa.subject.domain.provider.jdbc; + +import com.salpa.subject.domain.data.ColumnMeta; +import com.salpa.subject.domain.provider.ColumnMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +public class JdbcColumnMetaProvider implements ColumnMetaProvider { + + @Override + public List selectColumns(Connection connection, TableCondition tableCondition) { + try { + return doSelect(connection, tableCondition); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + private List doSelect(Connection connection, TableCondition tableCondition) throws SQLException { + List columnDocs = new ArrayList<>(); + String databaseName = tableCondition.getDatabaseName(); + String tableName = tableCondition.getTableName(); + List primaryKeyColumns = selectPrimaryKeyColumns(connection.getMetaData(), tableCondition); + ResultSet columnsResult; + try { + columnsResult = connection.getMetaData() + .getColumns(databaseName, tableCondition.getSchemaName(), tableName, null); + } catch (SQLException e) { + log.warn("warn: ignore columns in " + databaseName + "." + tableName + ", error: " + e.getMessage()); + return columnDocs; + } + try { + while (columnsResult.next()) { + String columnName = columnsResult.getString("COLUMN_NAME"); + if (tableCondition.columnIsIgnored(columnName)) { + if (log.isWarnEnabled()) { + log.warn("ignore column: " + columnName); + } + } else { + String defaultValue = columnsResult.getString("COLUMN_DEF"); + String isNullable = columnsResult.getString("IS_NULLABLE"); + if (isNullable.trim().equals("")) { + isNullable = "UNKNOWN"; + } + + if (defaultValue != null && defaultValue.trim().equals("")) { + defaultValue = "'" + defaultValue + "'"; + } + Integer decimalDigits; + if (columnsResult.getObject("DECIMAL_DIGITS") == null) { + decimalDigits = null; + } else { + decimalDigits = columnsResult.getInt("DECIMAL_DIGITS"); + } + Integer columnSize = columnsResult.getInt("COLUMN_SIZE"); + String columnType = columnsResult.getString("TYPE_NAME"); + String columnComment = columnsResult.getString("REMARKS"); + int dataType = columnsResult.getInt("DATA_TYPE"); + String autoIncrement = retrieveAutoIncrement(columnsResult); + ColumnMeta columnMeta = ColumnMeta.builder() + .name(columnName) + .dataType(dataType) + .type(columnType) + .size(columnSize) + .decimalDigits(decimalDigits) + .nullable(isNullable) + .autoIncrement(autoIncrement) + .comment(columnComment) + .defaultValue(defaultValue) + .isPrimaryKey(primaryKeyColumns.contains(columnName)) + .build(); + columnDocs.add(columnMeta); + } + } + } finally { + columnsResult.close(); + } + return columnDocs; + } + + private String retrieveAutoIncrement(ResultSet columnsResult) { + try { + return retrieveAutoIncrement(columnsResult, "IS_AUTOINCREMENT"); + } catch (SQLException e) { + log.warn("get is_autoincrement failed, fallback to is_auto_increment, error: " + e.getMessage()); + try { + // hive jdbc driver doesn't support is_autoincrement, fallback to is_auto_increment + return retrieveAutoIncrement(columnsResult, "is_auto_increment"); + } catch (SQLException ex) { + log.warn("get is_auto_increment failed, error: " + ex.getMessage()); + return "UNKNOWN"; + } + } + } + + private String retrieveAutoIncrement(ResultSet columnsResult, String columnName) throws SQLException { + String isAutoIncrement = columnsResult.getString(columnName); + if (isAutoIncrement.trim().equals("")) { + return "UNKNOWN"; + } + return isAutoIncrement; + } + + private List selectPrimaryKeyColumns(DatabaseMetaData meta, + TableCondition tableCondition) throws SQLException { + ResultSet result = meta.getPrimaryKeys(tableCondition.getDatabaseName(), + tableCondition.getSchemaName(), tableCondition.getTableName()); + try { + List columns = new ArrayList<>(); + while (result.next()) { + String columnName = result.getString("COLUMN_NAME"); + columns.add(columnName); + } + return columns; + } finally { + result.close(); + } + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcDatabaseMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcDatabaseMetaProvider.java new file mode 100644 index 0000000..d2f4e2b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcDatabaseMetaProvider.java @@ -0,0 +1,64 @@ +package com.salpa.subject.domain.provider.jdbc; + +import com.salpa.subject.domain.data.DatabaseMeta; +import com.salpa.subject.domain.data.TableMeta; +import com.salpa.subject.domain.provider.DatabaseMetaProvider; +import com.salpa.subject.domain.provider.TableMetaProvider; +import com.salpa.subject.domain.provider.condition.Condition; +import lombok.RequiredArgsConstructor; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@RequiredArgsConstructor +public class JdbcDatabaseMetaProvider implements DatabaseMetaProvider { + + private final TableMetaProvider tableMetaProvider; + + @Override + public Optional select(Connection connection, Condition condition) { + try { + DatabaseMetaData metaData = connection.getMetaData(); + ResultSet catalogs = metaData.getCatalogs(); + while (catalogs.next()) { + String catalogName = catalogs.getString("TABLE_CAT"); + if (Objects.equals(condition.getDatabaseName(), catalogName)) { + List tableDocs = tableMetaProvider.selectTables(connection, condition); + DatabaseMeta meta = DatabaseMeta.builder() + .productName(metaData.getDatabaseProductName()) + .productVersion(metaData.getDatabaseProductVersion()) + .databaseName(catalogName) + .schemaName(condition.getSchemaName()) + .tables(tableDocs) + .build(); + return Optional.of(meta); + } + } + + ResultSet schemas = metaData.getSchemas(); + while (schemas.next()) { + String schemaName = schemas.getString("TABLE_SCHEM"); + if (Objects.equals(condition.getSchemaName(), schemaName)) { + List tableDocs = tableMetaProvider.selectTables(connection, condition); + DatabaseMeta meta = DatabaseMeta.builder() + .productName(metaData.getDatabaseProductName()) + .productVersion(metaData.getDatabaseProductVersion()) + .databaseName(condition.getDatabaseName()) + .schemaName(condition.getSchemaName()) + .tables(tableDocs) + .build(); + return Optional.of(meta); + } + } + return Optional.empty(); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcForeignKeyMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcForeignKeyMetaProvider.java new file mode 100644 index 0000000..74e44a9 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcForeignKeyMetaProvider.java @@ -0,0 +1,92 @@ +package com.salpa.subject.domain.provider.jdbc; + +import com.salpa.subject.domain.data.ForeignKeyMeta; +import com.salpa.subject.domain.provider.ForeignKeyMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public class JdbcForeignKeyMetaProvider implements ForeignKeyMetaProvider { + + @Override + public List selectForeignKeys(Connection connection, TableCondition condition) { + String databaseName = condition.getDatabaseName(); + String schemaName = condition.getSchemaName(); + String tableName = condition.getTableName(); + List foreignKeys = new ArrayList<>(); + ResultSet keyResult = null; + try { + keyResult = connection.getMetaData().getImportedKeys(databaseName, schemaName, tableName); + while (keyResult.next()) { + String fkTableName = keyResult.getString("FKTABLE_NAME"); + String fkColumnName = keyResult.getString("FKCOLUMN_NAME"); + String fkName = keyResult.getString("FK_NAME"); + + String pkTableName = keyResult.getString("PKTABLE_NAME"); + String pkColumnName = keyResult.getString("PKCOLUMN_NAME"); + String pkName = keyResult.getString("PK_NAME"); + int updateRule = keyResult.getInt("UPDATE_RULE"); + int keySeq = keyResult.getInt("KEY_SEQ"); + int deleteRule = keyResult.getInt("DELETE_RULE"); + ForeignKeyMeta meta = ForeignKeyMeta.builder() + .keySeq(keySeq) + .fkTableName(fkTableName) + .fkColumnName(fkColumnName) + .fkName(fkName) + .pkTableName(pkTableName) + .pkColumnName(pkColumnName) + .pkName(pkName) + .updateRule(updateRuleConvert(updateRule)) + .deleteRule(deleteRuleConvert(deleteRule)) + .build(); + foreignKeys.add(meta); + } + } catch (SQLException e) { + log.warn("warn: ignore foreign keys in " + databaseName + "." + tableName + ", " + e.getMessage()); + } finally { + try { + if (keyResult != null) { + keyResult.close(); + } + } catch (SQLException e) { + log.warn("warn: close key result error " + databaseName + "." + tableName + ", " + e.getMessage()); + } + } + return foreignKeys; + } + + private String updateRuleConvert(int updateRule) { + return doMapping(updateRule, "update"); + } + + private String deleteRuleConvert(int deleteRule) { + return doMapping(deleteRule, "delete"); + } + + private String doMapping(int rule, String type) { + if (rule == DatabaseMetaData.importedKeyCascade) { + return "CASCADE"; + } + if (rule == DatabaseMetaData.importedKeyRestrict) { + return "CASCADE"; + } + if (rule == DatabaseMetaData.importedKeyNoAction) { + return "RESTRICT"; + } + if (rule == DatabaseMetaData.importedKeySetNull) { + return "SET_NULL"; + } + if (rule == DatabaseMetaData.importedKeySetDefault) { + return "SET_DEFAULT"; + } + log.warn("can not map foreign key " + type + " rule = " + rule); + return ""; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcIndexMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcIndexMetaProvider.java new file mode 100644 index 0000000..525f0fd --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcIndexMetaProvider.java @@ -0,0 +1,66 @@ +package com.salpa.subject.domain.provider.jdbc; + +import com.salpa.subject.domain.data.IndexMeta; +import com.salpa.subject.domain.provider.IndexMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +@Slf4j +public class JdbcIndexMetaProvider implements IndexMetaProvider { + @Override + public List selectIndexes(Connection connection, TableCondition condition) { + try { + return doCreateIndexDocs(connection, condition); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + private List doCreateIndexDocs(Connection connection, TableCondition condition) + throws SQLException { + String databaseName = condition.getDatabaseName(); + String tableName = condition.getTableName(); + List indexMetas = new ArrayList<>(); + ResultSet indexResults; + try { + indexResults = connection.getMetaData() + .getIndexInfo(databaseName, condition.getSchemaName(), tableName, false, false); + } catch (SQLException e) { + log.warn("warn: ignore " + databaseName + "." + tableName + ", error=" + e.getMessage()); + return indexMetas; + } + + Map metaGroupByName = new HashMap<>(); + try { + while (indexResults.next()) { + Boolean nonUnique = indexResults.getBoolean("NON_UNIQUE"); + String indexName = indexResults.getString("INDEX_NAME"); + String columnName = indexResults.getString("COLUMN_NAME"); + if (indexName == null) { + continue; + } + if (metaGroupByName.containsKey(indexName)) { + metaGroupByName.get(indexName).getColumnNames().add(columnName); + } else { + List columns = new ArrayList<>(); + columns.add(columnName); + IndexMeta indexMeta = IndexMeta.builder() + .name(indexName) + .columnNames(columns) + .isUniqueKey(Objects.equals(nonUnique, false)) + .build(); + metaGroupByName.put(indexName, indexMeta); + } + } + } finally { + indexResults.close(); + } + return new ArrayList<>(metaGroupByName.values()); + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcTableMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcTableMetaProvider.java new file mode 100644 index 0000000..bb787ff --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcTableMetaProvider.java @@ -0,0 +1,81 @@ +package com.salpa.subject.domain.provider.jdbc; + +import com.salpa.subject.domain.data.ColumnMeta; +import com.salpa.subject.domain.data.TableMeta; +import com.salpa.subject.domain.provider.*; +import com.salpa.subject.domain.provider.condition.Condition; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +@Slf4j +public class JdbcTableMetaProvider implements TableMetaProvider { + + private final ColumnMetaProvider columnMetaProvider; + + private final IndexMetaProvider indexMetaProvider; + + private final TriggerMetaProvider triggerMetaProvider; + + private final ForeignKeyMetaProvider foreignKeyMetaProvider; + + @Override + public List selectTables(Connection connection, Condition condition) { + try { + return doSelect(connection, condition); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + private List doSelect(Connection connection, Condition condition) throws SQLException { + List tableMetas = new ArrayList<>(); + String databaseName = condition.getDatabaseName(); + String[] tableTypes = {"TABLE", "VIEW"}; + ResultSet tablesResult = connection.getMetaData() + .getTables(databaseName, condition.getSchemaName(), null, tableTypes); + try { + while (tablesResult.next()) { + String tableName = tablesResult.getString("TABLE_NAME"); + if (condition.tableIsIgnored(tableName)) { + if (log.isWarnEnabled()) { + log.warn("ignored table: " + databaseName + "." + tableName); + } + } else { + String tableType = tablesResult.getString("TABLE_TYPE"); + String tableComment = tablesResult.getString("REMARKS"); + TableCondition tableCondition = TableCondition.of(condition, tableName); + List columns = columnMetaProvider.selectColumns(connection, tableCondition); + if (columns.isEmpty()) { + if (log.isWarnEnabled()) { + log.warn("ignored table: " + databaseName + "." + tableName + + ", caused by get empty columns"); + } + continue; + } + TableMeta tableMeta = TableMeta.builder() + .name(tableName) + .type(tableType) + .comment(tableComment) + .columns(columns) + .foreignKeys(foreignKeyMetaProvider.selectForeignKeys(connection, tableCondition)) + .indexes(indexMetaProvider.selectIndexes(connection, tableCondition)) + .triggers(triggerMetaProvider.selectTriggers(connection, tableCondition)) + .build(); + tableMetas.add(tableMeta); + } + } + } finally { + tablesResult.close(); + } + return tableMetas; + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcTriggerMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcTriggerMetaProvider.java new file mode 100644 index 0000000..f11fe3a --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/jdbc/JdbcTriggerMetaProvider.java @@ -0,0 +1,19 @@ +package com.salpa.subject.domain.provider.jdbc; + + +import com.salpa.subject.domain.data.TriggerMeta; +import com.salpa.subject.domain.provider.TriggerMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; + +import java.sql.Connection; +import java.util.Collections; +import java.util.List; + +public class JdbcTriggerMetaProvider implements TriggerMetaProvider { + + @Override + public List selectTriggers(Connection connection, TableCondition condition) { + // note: jdbc not support get triggers + return Collections.emptyList(); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/maria/MariaTriggerMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/maria/MariaTriggerMetaProvider.java new file mode 100644 index 0000000..b687567 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/maria/MariaTriggerMetaProvider.java @@ -0,0 +1,26 @@ +package com.salpa.subject.domain.provider.maria; + +import com.salpa.subject.domain.provider.AbstractSqlTriggerMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class MariaTriggerMetaProvider extends AbstractSqlTriggerMetaProvider { + @Override + protected String sql(TableCondition condition) { + String sql = "SELECT \n" + + " TRIGGER_NAME,\n" + + " TRIGGER_SCHEMA,\n" + + " TRIGGER_CATALOG,\n" + + " EVENT_OBJECT_CATALOG,\n" + + " EVENT_OBJECT_SCHEMA,\n" + + " EVENT_OBJECT_TABLE,\n" + + " ACTION_STATEMENT,\n" + + " ACTION_TIMING,\n" + + " EVENT_MANIPULATION,\n" + + " CREATED\n" + + "FROM information_schema.triggers " + + "WHERE TRIGGER_SCHEMA = '%s' AND EVENT_OBJECT_TABLE = '%s'"; + return String.format(sql, condition.getDatabaseName(), condition.getTableName()); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/mysql/MysqlTableTriggerMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/mysql/MysqlTableTriggerMetaProvider.java new file mode 100644 index 0000000..236423d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/mysql/MysqlTableTriggerMetaProvider.java @@ -0,0 +1,34 @@ +package com.salpa.subject.domain.provider.mysql; + +import com.salpa.subject.domain.provider.AbstractSqlTriggerMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class MysqlTableTriggerMetaProvider extends AbstractSqlTriggerMetaProvider { + @Override + protected String sql(TableCondition condition) { + String sql = "SELECT TRIGGER_CATALOG,\n" + + " TRIGGER_SCHEMA,\n" + + " TRIGGER_NAME,\n" + + " EVENT_MANIPULATION,\n" + + " EVENT_OBJECT_CATALOG,\n" + + " EVENT_OBJECT_SCHEMA,\n" + + " EVENT_OBJECT_TABLE,\n" + + " ACTION_ORDER,\n" + + " ACTION_CONDITION,\n" + + " ACTION_STATEMENT,\n" + + " ACTION_ORIENTATION,\n" + + " ACTION_TIMING,\n" + + " ACTION_REFERENCE_OLD_TABLE,\n" + + " ACTION_REFERENCE_NEW_TABLE,\n" + + " ACTION_REFERENCE_OLD_ROW,\n" + + " ACTION_REFERENCE_NEW_ROW,\n" + + " CREATED,\n" + + " SQL_MODE,\n" + + " DEFINER\n " + + "FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_SCHEMA = '%s' AND EVENT_OBJECT_TABLE = '%s'"; + return String.format(sql, condition.getDatabaseName(), condition.getTableName()); + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/oracle/OracleTriggerMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/oracle/OracleTriggerMetaProvider.java new file mode 100644 index 0000000..e341629 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/oracle/OracleTriggerMetaProvider.java @@ -0,0 +1,44 @@ +package com.salpa.subject.domain.provider.oracle; + +import com.salpa.subject.domain.provider.AbstractSqlTriggerMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.extern.slf4j.Slf4j; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Objects; + +@Slf4j +public class OracleTriggerMetaProvider extends AbstractSqlTriggerMetaProvider { + @Override + protected String sql(TableCondition condition) { + String sql = "SELECT trig.table_owner AS schema_name,\n" + + " trig.table_name,\n" + + " trig.owner AS trigger_schema_name,\n" + + " trig.trigger_name as TRIGGER_NAME,\n" + + " trig.trigger_type AS ACTION_TIMING,\n" + + " trig.triggering_event as EVENT_MANIPULATION,\n" + + " trig.status,\n" + + " trig.trigger_body AS ACTION_STATEMENT \n" + + "FROM sys.all_triggers trig\n" + + " INNER JOIN sys.all_tables tab ON trig.table_owner = tab.owner\n" + + " AND trig.table_name = tab.table_name\n" + + "WHERE trig.base_object_type = 'TABLE' AND trig.owner = '%s' AND trig.TABLE_NAME = '%s'"; + return String.format(sql, condition.getSchemaName(), condition.getTableName()); + } + + @Override + protected String getTriggerName(ResultSet results) throws SQLException { + String status = results.getString("status"); + String name = Objects.requireNonNullElse(results.getString("trigger_name"), "") + + " (" + + status + + ")"; + return name; + } + + @Override + protected String getCreateAt(ResultSet results) throws SQLException { + return "unknown"; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/postgresql/PostgresqlTriggerMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/postgresql/PostgresqlTriggerMetaProvider.java new file mode 100644 index 0000000..f9db376 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/postgresql/PostgresqlTriggerMetaProvider.java @@ -0,0 +1,25 @@ +package com.salpa.subject.domain.provider.postgresql; + +import com.salpa.subject.domain.provider.AbstractSqlTriggerMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class PostgresqlTriggerMetaProvider extends AbstractSqlTriggerMetaProvider { + @Override + protected String sql(TableCondition condition) { + String sql = "SELECT trigger_name AS TRIGGER_NAME,\n" + + " action_timing AS ACTION_TIMING,\n" + + " event_manipulation AS EVENT_MANIPULATION,\n" + + " action_statement AS ACTION_STATEMENT,\n" + + " created AS CREATED,\n" + + " trigger_catalog AS trigger_catalog,\n" + + " TRIGGER_SCHEMA AS trigger_schema,\n" + + " event_object_catalog AS target_catalog,\n" + + " event_object_schema AS target_schema,\n" + + " event_object_table AS target_table_name\n" + + "FROM information_schema.triggers " + + "WHERE trigger_catalog = '%s' AND trigger_schema = '%s' AND event_object_table = '%s';"; + return String.format(sql, condition.getDatabaseName(), condition.getSchemaName(), condition.getTableName()); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerColumnMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerColumnMetaProvider.java new file mode 100644 index 0000000..39b3fea --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerColumnMetaProvider.java @@ -0,0 +1,78 @@ +package com.salpa.subject.domain.provider.sqlserver; + +import com.salpa.subject.domain.data.ColumnMeta; +import com.salpa.subject.domain.provider.ColumnMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import com.salpa.subject.domain.provider.jdbc.JdbcColumnMetaProvider; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +public class SqlServerColumnMetaProvider implements ColumnMetaProvider { + + private final ColumnMetaProvider columnMetaProvider = new JdbcColumnMetaProvider(); + + @Override + public List selectColumns(Connection connection, TableCondition condition) { + Map columnRemarksMap = getColumnRemarks(connection, condition); + return columnMetaProvider.selectColumns(connection, condition) + .stream() + .map(column -> { + String remark = columnRemarksMap.getOrDefault(column.getName(), ""); + column.setComment(remark); + return column; + }) + .collect(Collectors.toList()); + } + + private Map getColumnRemarks(Connection connection, + TableCondition condition) { + String sql = "SELECT col.name AS COLUMN_NAME,\n" + + " ep.value AS REMARKS\n" + + "FROM sys.tables AS tab\n" + + " INNER JOIN sys.columns AS col\n" + + " ON tab.object_id = col.object_id\n" + + " LEFT JOIN sys.extended_properties AS ep " + + "ON ep.major_id = col.object_id AND ep.minor_id = col.column_id\n" + + "WHERE tab.name LIKE ?\n" + + " AND SCHEMA_NAME(tab.schema_id) LIKE ?\n" + + "ORDER BY tab.name, column_id;"; + + Map map = new HashMap<>(); + PreparedStatement preparedStatement = null; + try { + preparedStatement = connection.prepareStatement(sql); + preparedStatement.setString(1, condition.getTableName()); + preparedStatement.setString(2, condition.getSchemaName()); + ResultSet result = preparedStatement.executeQuery(); + while (result.next()) { + String name = result.getString("COLUMN_NAME"); + String remarks = result.getString("REMARKS"); + if (name == null || remarks == null) { + continue; + } else { + map.put(name, remarks); + } + } + } catch (SQLException e) { + log.error("", e); + } finally { + if (preparedStatement != null) { + try { + preparedStatement.close(); + } catch (SQLException e) { + // ignore + } + } + } + return map; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerTableMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerTableMetaProvider.java new file mode 100644 index 0000000..9621a51 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerTableMetaProvider.java @@ -0,0 +1,146 @@ +package com.salpa.subject.domain.provider.sqlserver; + +import com.salpa.subject.domain.data.ColumnMeta; +import com.salpa.subject.domain.data.TableMeta; +import com.salpa.subject.domain.provider.*; +import com.salpa.subject.domain.provider.condition.Condition; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +@RequiredArgsConstructor +@Slf4j +public class SqlServerTableMetaProvider implements TableMetaProvider { + + private final ColumnMetaProvider columnMetaProvider; + + private final IndexMetaProvider indexMetaProvider; + + private final ForeignKeyMetaProvider foreignKeyMetaProvider; + + private final TriggerMetaProvider triggerMetaProvider; + + @Override + public List selectTables(Connection connection, Condition condition) { + return doSelect(connection, condition); + } + + private List doSelect(Connection connection, Condition condition) { + List tableMetas = new ArrayList<>(); + String databaseName = condition.getDatabaseName(); + Map tableNameAndComment = tableNameAndCommentMap(connection, condition); + ResultSet tablesResult = null; + try { + tablesResult = connection.getMetaData() + .getTables(databaseName, condition.getSchemaName(), null, new String[]{"TABLE"}); + while (tablesResult.next()) { + String tableName = tablesResult.getString("TABLE_NAME"); + if (condition.tableIsIgnored(tableName)) { + if (log.isDebugEnabled()) { + log.debug("ignored table: " + databaseName + "." + tableName); + } + } else { + String tableType = tablesResult.getString("TABLE_TYPE"); + String tableComment = tableNameAndComment.getOrDefault(tableName, ""); + TableCondition tableCondition = TableCondition.of(condition, tableName); + List columns = columnMetaProvider.selectColumns(connection, tableCondition); + if (columns.isEmpty()) { + if (log.isWarnEnabled()) { + log.warn("ignored table: " + databaseName + "." + tableName + + ", caused by get empty columns"); + } + continue; + } + TableMeta tableMeta = TableMeta.builder() + .name(tableName) + .type(tableType) + .comment(tableComment) + .columns(columns) + .foreignKeys(foreignKeyMetaProvider.selectForeignKeys(connection, tableCondition)) + .indexes(indexMetaProvider.selectIndexes(connection, tableCondition)) + .triggers(triggerMetaProvider.selectTriggers(connection, tableCondition)) + .build(); + tableMetas.add(tableMeta); + } + } + } catch (SQLException e) { + log.error("select tables error", e); + throw new IllegalStateException(e); + } finally { + if (tablesResult != null) { + try { + tablesResult.close(); + } catch (SQLException e) { + // ignore + } + } + } + return tableMetas; + } + + public Map tableNameAndCommentMap(Connection connection, Condition condition) { + String sql = "SELECT sys.objects.name AS TABLE_NAME,\n" + + " sys.objects.type_desc AS TABLE_TYPE,\n" + + " CAST(extended_properties.value AS NVARCHAR(500)) AS REMARKS\n" + + "FROM sys.objects\n" + + " LEFT JOIN sys.schemas ON sys.objects.schema_id = sys.schemas.schema_id\n" + + " LEFT JOIN sys.extended_properties " + + "ON sys.objects.object_id = sys.extended_properties.major_id\n" + + "WHERE (type = 'U' OR type = 'V')\n" + + " AND sys.extended_properties.minor_id = 0\n" + + " AND sys.schemas.name LIKE ?;\n"; + PreparedStatement preparedStatement = null; + try { + preparedStatement = connection.prepareStatement(sql); + preparedStatement.setString(1, condition.getSchemaName()); + ResultSet results = preparedStatement.executeQuery(); + return tableNameAndCommentMap(results, connection, condition); + } catch (SQLException e) { + log.warn("get table meta failed {}", e.getMessage()); + if (log.isDebugEnabled()) { + log.debug("get table meta failed", e); + } + return Collections.emptyMap(); + } finally { + if (preparedStatement != null) { + try { + preparedStatement.close(); + } catch (SQLException e) { + // ignore + } + } + } + } + + private Map tableNameAndCommentMap(ResultSet tablesResult, + Connection connection, + Condition condition) throws SQLException { + + Map tableNameAndCommentMap = new HashMap<>(); + try { + while (tablesResult.next()) { + String tableName = tablesResult.getString("TABLE_NAME"); + if (condition.tableIsIgnored(tableName)) { + if (log.isDebugEnabled()) { + log.debug("ignored table: " + condition.getSchemaName() + "." + tableName); + } + } else { + String tableComment = tablesResult.getString("REMARKS"); + if (!Objects.isNull(tableComment)) { + tableNameAndCommentMap.put(tableName, tableComment); + } + } + } + } finally { + tablesResult.close(); + } + return tableNameAndCommentMap; + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerTriggerMetaProvider.java b/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerTriggerMetaProvider.java new file mode 100644 index 0000000..7b9c46c --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/provider/sqlserver/SqlServerTriggerMetaProvider.java @@ -0,0 +1,45 @@ +package com.salpa.subject.domain.provider.sqlserver; + +import com.salpa.subject.domain.provider.AbstractSqlTriggerMetaProvider; +import com.salpa.subject.domain.provider.condition.TableCondition; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SqlServerTriggerMetaProvider extends AbstractSqlTriggerMetaProvider { + @Override + protected String sql(TableCondition condition) { + String sql = "SELECT SCHEMA_NAME(tab.schema_id) + '.' + tab.name AS table_name,\n" + + " trig.name AS TRIGGER_NAME,\n" + + " trig.create_date AS CREATED,\n" + + " CASE\n" + + " WHEN is_instead_of_trigger = 1 THEN 'Instead of'\n" + + " ELSE 'After' END AS ACTION_TIMING,\n" + + " (CASE\n" + + " WHEN OBJECTPROPERTY(trig.object_id, 'ExecIsUpdateTrigger') = 1\n" + + " THEN 'Update '\n" + + " ELSE '' END\n" + + " + CASE\n" + + " WHEN OBJECTPROPERTY(trig.object_id, 'ExecIsDeleteTrigger') = 1\n" + + " THEN 'Delete '\n" + + " ELSE '' END\n" + + " + CASE\n" + + " WHEN OBJECTPROPERTY(trig.object_id, 'ExecIsInsertTrigger') = 1\n" + + " THEN 'Insert '\n" + + " ELSE '' END\n" + + " ) AS EVENT_MANIPULATION,\n" + + " CASE\n" + + " WHEN trig.[type] = 'TA' THEN 'Assembly (CLR) trigger'\n" + + " WHEN trig.[type] = 'TR' THEN 'SQL trigger'\n" + + " ELSE '' END AS [TYPE],\n" + + " CASE\n" + + " WHEN is_disabled = 1 THEN 'Disabled'\n" + + " ELSE 'Active' END AS [status],\n" + + " OBJECT_DEFINITION(trig.object_id) AS ACTION_STATEMENT\n" + + "FROM sys.triggers trig\n" + + " INNER JOIN sys.objects tab\n" + + " ON trig.parent_id = tab.object_id\n" + + "WHERE SCHEMA_NAME(tab.schema_id) = '%s' AND tab.name = '%s'"; + return String.format(sql, condition.getSchemaName(), condition.getTableName()); + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/render/Render.java b/salpa-system/src/main/java/com/salpa/subject/domain/render/Render.java new file mode 100644 index 0000000..4cfcc1b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/render/Render.java @@ -0,0 +1,19 @@ +package com.salpa.subject.domain.render; + + + +import com.salpa.subject.domain.data.DatabaseMeta; +import com.salpa.subject.domain.render.markdown.MarkdownTemplateRender; + +import java.io.IOException; +import java.io.OutputStream; + +public interface Render { + + void rendering(DatabaseMeta meta, OutputStream outputStream) throws IOException; + + static Render markdownRender(RenderConfig configuration) { + return new MarkdownTemplateRender(configuration); + } + +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/render/RenderConfig.java b/salpa-system/src/main/java/com/salpa/subject/domain/render/RenderConfig.java new file mode 100644 index 0000000..976f1d4 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/render/RenderConfig.java @@ -0,0 +1,65 @@ +package com.salpa.subject.domain.render; + +import com.salpa.subject.domain.data.ColumnMeta; +import com.salpa.subject.domain.data.IndexMeta; +import com.salpa.subject.domain.data.TriggerMeta; +import lombok.Data; + +import java.util.LinkedHashMap; +import java.util.function.Function; + +@Data +public class RenderConfig { + + private Boolean renderTables = true; + + private Boolean renderColumns = true; + + private Boolean renderIndexes = true; + + private Boolean renderTriggers = true; + + private LinkedHashMap> columnTitleAndValueMapping = + columnTitleAndValueMapping(); + + private LinkedHashMap> indexTitleAndValueMapping = + indexTitleAndValueMapping(); + + private LinkedHashMap> triggerTitleAndValueMapping = + triggerTitleAndValueMapping(); + + protected LinkedHashMap> columnTitleAndValueMapping() { + LinkedHashMap> mapping = new LinkedHashMap<>(); + mapping.put("Name", ColumnMeta::getName); + mapping.put("Type", column -> { + String type; + if (column.getDecimalDigits() == null || column.getDecimalDigits().equals(0)) { + type = column.getType() + + "(" + column.getSize().toString() + ")"; + } else { + type = column.getType() + + "(" + column.getSize().toString() + ", " + column.getDecimalDigits().toString() + ")"; + } + return type; + }); + return mapping; + } + + protected LinkedHashMap> indexTitleAndValueMapping() { + LinkedHashMap> mapping = new LinkedHashMap<>(); + mapping.put("Name", IndexMeta::getName); + mapping.put("IsUnique", index -> index.getIsUniqueKey() ? "YES" : ""); + mapping.put("Columns", index -> String.join(", ", index.getColumnNames())); + return mapping; + } + + protected LinkedHashMap> triggerTitleAndValueMapping() { + LinkedHashMap> mapping = new LinkedHashMap<>(); + mapping.put("Name", TriggerMeta::getName); + mapping.put("Timing", trigger -> trigger.getTiming() + " " + trigger.getManipulation()); + mapping.put("Statement", trigger -> trigger.getStatement().replace("\n", " ") + .replace("\r", " ")); + mapping.put("Create At", TriggerMeta::getCreateAt); + return mapping; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownBuilder.java b/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownBuilder.java new file mode 100644 index 0000000..af15227 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownBuilder.java @@ -0,0 +1,111 @@ +package com.salpa.subject.domain.render.markdown; + +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MarkdownBuilder { + + private static final String LINE = "\n"; + + private static final String DOUBLE_LINE = LINE + LINE; + + private StringBuilder builder = new StringBuilder(1024); + + private MarkdownBuilder() { + } + + public static MarkdownBuilder builder() { + return new MarkdownBuilder(); + } + + public MarkdownBuilder primaryTitle(String title) { + builder.append("# ").append(title).append(DOUBLE_LINE); + return this; + } + + public MarkdownBuilder secondTitle(String title) { + builder.append("## ").append(title).append(DOUBLE_LINE); + return this; + } + + public MarkdownBuilder thirdTitle(String title) { + builder.append("### ").append(title).append(DOUBLE_LINE); + return this; + } + + public MarkdownBuilder text(String text) { + builder.append(text).append(DOUBLE_LINE); + return this; + } + + public MarkdownBuilder table(List titles, List> rows) { + if (titles == null || titles.isEmpty()) { + throw new IllegalArgumentException("titles must not be null or empty"); + } + // build titles + builder.append("| "); + for (String title : titles) { + builder.append(title).append(" | "); + } + builder.append(LINE); + + // build separators + builder.append("| "); + for (String title : titles) { + builder.append("------").append(" | "); + } + builder.append(LINE); + + // build rows + for (List row : rows) { + builder.append("| "); + for (String column : row) { + builder.append(column).append(" | "); + } + builder.append(LINE); + } + builder.append(LINE); + return this; + } + + public MarkdownBuilder orderedList(List list) { + for (int i = 0; i < list.size(); i++) { + builder.append(i + 1).append(". ").append(list.get(i)).append(LINE); + } + builder.append(LINE); + return this; + } + + public MarkdownBuilder unorderedList(List list) { + for (String item : list) { + builder.append("- ").append(item).append(LINE); + } + builder.append(LINE); + return this; + } + + public MarkdownBuilder blockquotes(String content) { + builder.append("> ").append(content).append(DOUBLE_LINE); + return this; + } + + public MarkdownBuilder code(String languageType, String statement) { + builder.append("```").append(languageType).append(LINE) + .append(statement) + .append("```") + .append(DOUBLE_LINE); + return this; + } + + public MarkdownBuilder link(String text, String link) { + builder.append("[").append(text).append("]") + .append("(").append(link).append(")"); + return this; + } + + public String build() { + return builder.toString(); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownRender.java b/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownRender.java new file mode 100644 index 0000000..867f7cf --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownRender.java @@ -0,0 +1,114 @@ +package com.salpa.subject.domain.render.markdown; + +import com.salpa.subject.domain.data.DatabaseMeta; +import com.salpa.subject.domain.data.TableMeta; +import com.salpa.subject.domain.render.Render; +import com.salpa.subject.domain.render.RenderConfig; +import lombok.Getter; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class MarkdownRender implements Render { + + @Getter + private final RenderConfig config; + + protected MarkdownRender(RenderConfig config) { + this.config = config; + } + + public static MarkdownRender of(RenderConfig config) { + return new MarkdownRender(config); + } + + @Override + public void rendering(DatabaseMeta meta, + OutputStream outputStream) throws IOException { + MarkdownBuilder contentBuilder = MarkdownBuilder.builder(); + contentBuilder.primaryTitle(meta.getDatabaseName()); + if (config.getRenderTables()) { + for (TableMeta table : meta.getTables()) { + buildTableName(contentBuilder, table); + if (config.getRenderColumns()) { + buildColumns(contentBuilder, table); + } + if (config.getRenderIndexes()) { + buildIndexes(contentBuilder, table); + } + if (config.getRenderTriggers()) { + buildTriggers(contentBuilder, table); + } + } + } + outputStream.write(contentBuilder.build().getBytes(StandardCharsets.UTF_8)); + } + + private void buildTableName(MarkdownBuilder contentBuilder, TableMeta table) { + String tableName; + if (table.getComment().isEmpty()) { + tableName = table.getName(); + } else { + tableName = table.getName() + "(" + table.getComment() + ")"; + } + contentBuilder.secondTitle(tableName); + } + + private void buildColumns(MarkdownBuilder contentBuilder, TableMeta table) { + contentBuilder.unorderedList(Collections.singletonList("columns")); + List> allColumnRows = table.getColumns() + .stream() + .map(column -> config.getColumnTitleAndValueMapping() + .values() + .stream() + .map(mapping -> mapping.apply(column)) + .collect(Collectors.toList())) + .collect(Collectors.toList()); + contentBuilder.table(tableTitles(), allColumnRows); + } + + private void buildIndexes(MarkdownBuilder contentBuilder, TableMeta table) { + contentBuilder.unorderedList(Collections.singletonList("indexes")); + List> allIndexRows = table.getIndexes().stream() + .map(index -> config.getIndexTitleAndValueMapping() + .values() + .stream() + .map(mapping -> mapping.apply(index)) + .collect(Collectors.toList())) + .collect(Collectors.toList()); + contentBuilder.table(indexTitles(), allIndexRows); + } + + private void buildTriggers(MarkdownBuilder contentBuilder, TableMeta table) { + if (table.getTriggers() == null || table.getTriggers().isEmpty()) { + return; + } + + contentBuilder.unorderedList(Collections.singletonList("triggers")); + List> allRows = table.getTriggers().stream() + .map(trigger -> config.getTriggerTitleAndValueMapping() + .values() + .stream() + .map(mapping -> mapping.apply(trigger)) + .collect(Collectors.toList())) + .collect(Collectors.toList()); + contentBuilder.table(triggerTitles(), allRows); + } + + private List tableTitles() { + return new ArrayList<>(config.getColumnTitleAndValueMapping().keySet()); + } + + private List indexTitles() { + return new ArrayList<>(config.getIndexTitleAndValueMapping().keySet()); + } + + private List triggerTitles() { + return new ArrayList<>(config.getTriggerTitleAndValueMapping().keySet()); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownTemplateRender.java b/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownTemplateRender.java new file mode 100644 index 0000000..ecb165e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/render/markdown/MarkdownTemplateRender.java @@ -0,0 +1,57 @@ +package com.salpa.subject.domain.render.markdown; + +import com.salpa.subject.domain.data.DatabaseMeta; +import com.salpa.subject.domain.render.Render; +import com.salpa.subject.domain.render.RenderConfig; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.TemplateExceptionHandler; +import lombok.RequiredArgsConstructor; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * use freemarker template to render markdown + */ +@RequiredArgsConstructor +public class MarkdownTemplateRender implements Render { + + private final RenderConfig renderConfig; + + private String templatePath = "template/render/markdown/markdown.ftlh"; + + public MarkdownTemplateRender(RenderConfig config, String templatePath) { + this(config); + this.templatePath = templatePath; + } + + @Override + public void rendering(DatabaseMeta meta, OutputStream outputStream) throws IOException { + doRendering(meta, outputStream); + } + + public void doRendering(DatabaseMeta meta, OutputStream outputStream) throws IOException { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_29); + cfg.setClassForTemplateLoading(getClass(), "/"); + cfg.setDefaultEncoding("UTF-8"); + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + cfg.setLogTemplateExceptions(false); + cfg.setWrapUncheckedExceptions(true); + cfg.setFallbackOnNullLoopVariable(false); + + Map root = new HashMap<>(); + root.put("meta", meta); + root.put("config", renderConfig); + Template template = cfg.getTemplate(templatePath); + try { + template.process(root, new OutputStreamWriter(outputStream)); + } catch (TemplateException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/ColumnVo.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/ColumnVo.java new file mode 100644 index 0000000..031aa9b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/ColumnVo.java @@ -0,0 +1,74 @@ +package com.salpa.subject.domain.vo; + +public class ColumnVo { + + private String name; + + private String type; + + private String primaryKey; + + private String nullable; + + private String autoIncrement; + + private String defaultValue; + + private String comment; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getPrimaryKey() { + return primaryKey; + } + + public void setPrimaryKey(String primaryKey) { + this.primaryKey = primaryKey; + } + + public String getNullable() { + return nullable; + } + + public void setNullable(String nullable) { + this.nullable = nullable; + } + + public String getAutoIncrement() { + return autoIncrement; + } + + public void setAutoIncrement(String autoIncrement) { + this.autoIncrement = autoIncrement; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/DocumentDiscussionVo.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/DocumentDiscussionVo.java new file mode 100644 index 0000000..3c7567e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/DocumentDiscussionVo.java @@ -0,0 +1,63 @@ +package com.salpa.subject.domain.vo; + +/** + * 获取评论列表参数 + */ +public class DocumentDiscussionVo { + + /*页数*/ + private Integer pageNum; + + /*条数*/ + private Integer pageSize; + + /*模型id*/ + private Integer projectId; + + /*表名*/ + private String tableName; + + /*字段名*/ + private String columnName; + + + 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 Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getColumnName() { + return columnName; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/DocumentVo.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/DocumentVo.java new file mode 100644 index 0000000..ef80985 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/DocumentVo.java @@ -0,0 +1,19 @@ +package com.salpa.subject.domain.vo; + +/** + * 主题详情请求实体 + */ +public class DocumentVo { + + /*主题模型id*/ + private Integer projectId; + + /*当前版本*/ + private Long version; + + /*列表筛选条件*/ + private String search; + + /*原始版本*/ + private Long originalVersion; +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/DriverResult.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/DriverResult.java new file mode 100644 index 0000000..7961726 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/DriverResult.java @@ -0,0 +1,38 @@ +package com.salpa.subject.domain.vo; + + +import java.io.File; + + +public class DriverResult { + + private String driverFilePath; + + private File driverFile; + + private String message; + + public String getDriverFilePath() { + return driverFilePath; + } + + public void setDriverFilePath(String driverFilePath) { + this.driverFilePath = driverFilePath; + } + + public File getDriverFile() { + return driverFile; + } + + public void setDriverFile(File driverFile) { + this.driverFile = driverFile; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/MonitorDataSourceVo.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/MonitorDataSourceVo.java new file mode 100644 index 0000000..13608be --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/MonitorDataSourceVo.java @@ -0,0 +1,84 @@ +package com.salpa.subject.domain.vo; + +/** + * 分页请求 + */ +public class MonitorDataSourceVo { + + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 主题id */ + private Integer subjectId; + + /** 项目名称 */ + private String projectName; + + /** 数据库名称 */ + private String databaseName; + + /** schema名称 */ + private String schemaName; + + /** 数据库类型 */ + private String databaseType; + + 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 Integer getSubjectId() { + return subjectId; + } + + public void setSubjectId(Integer subjectId) { + this.subjectId = subjectId; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + public String getDatabaseType() { + return databaseType; + } + + public void setDatabaseType(String databaseType) { + this.databaseType = databaseType; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/MonitorSubjectVo.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/MonitorSubjectVo.java new file mode 100644 index 0000000..ca25ca2 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/MonitorSubjectVo.java @@ -0,0 +1,40 @@ +package com.salpa.subject.domain.vo; + +/** + * 主题管理分页请求 + */ +public class MonitorSubjectVo { + + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 主题名称 */ + private String subjectName; + + 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 getSubjectName() { + return subjectName; + } + + public void setSubjectName(String subjectName) { + this.subjectName = subjectName; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/OperationLogVo.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/OperationLogVo.java new file mode 100644 index 0000000..73c8c73 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/OperationLogVo.java @@ -0,0 +1,43 @@ +package com.salpa.subject.domain.vo; + +/** + * 日志分页请求 + * + * @author zhuff + */ +public class OperationLogVo { + + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 模型id */ + private Integer projectId; + + + 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 Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/ProjectSyncRule.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/ProjectSyncRule.java new file mode 100644 index 0000000..cd903e0 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/ProjectSyncRule.java @@ -0,0 +1,65 @@ +package com.salpa.subject.domain.vo; + +/** + * 数据库高级设置 + * + * @author zhuff + */ +public class ProjectSyncRule { + + /** 项目ID */ + private Integer projectId; + + /** 忽略表名称 */ + private String[] ignoreTableNameRegexArray; + + /** 忽略字段名称 */ + private String[] ignoreColumnNameRegexArray; + + /** 是否定时同步 */ + private boolean autoSync; + + /** cron表达式 */ + private String autoSyncCron; + + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String[] getIgnoreTableNameRegexArray() { + return ignoreTableNameRegexArray; + } + + public void setIgnoreTableNameRegexArray(String[] ignoreTableNameRegexArray) { + this.ignoreTableNameRegexArray = ignoreTableNameRegexArray; + } + + public String[] getIgnoreColumnNameRegexArray() { + return ignoreColumnNameRegexArray; + } + + public void setIgnoreColumnNameRegexArray(String[] ignoreColumnNameRegexArray) { + this.ignoreColumnNameRegexArray = ignoreColumnNameRegexArray; + } + + public boolean isAutoSync() { + return autoSync; + } + + public void setAutoSync(boolean autoSync) { + this.autoSync = autoSync; + } + + public String getAutoSyncCron() { + return autoSyncCron; + } + + public void setAutoSyncCron(String autoSyncCron) { + this.autoSyncCron = autoSyncCron; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/domain/vo/ProjectVo.java b/salpa-system/src/main/java/com/salpa/subject/domain/vo/ProjectVo.java new file mode 100644 index 0000000..9fc525d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/domain/vo/ProjectVo.java @@ -0,0 +1,176 @@ +package com.salpa.subject.domain.vo; + +import java.util.Map; + +/** + * 项目创建实体 + * + * @author zhuff + */ +public class ProjectVo { + + /** 项目ID */ + private Integer projectId; + + /** 项目名称 */ + private String projectName; + + /** 项目名称 */ + private String projectDescription; + + /** 主题id */ + private Integer subjectId; + + /** 数据库名称 */ + private String databaseName; + + /** 主要描述 */ + private String schemaName; + + /** 数据库类型 */ + private String databaseType; + + /** 数据库路径 */ + private String url; + + /** 登录名 */ + private String username; + + /** 密码 */ + private String password; + + /** 连接参数 */ + private Map[] properties; + + /** 忽略表名称 */ + private String[] ignoreTableNameRegexArray; + + /** 忽略字段名称 */ + private String[] ignoreColumnNameRegexArray; + + /** 是否定时同步 */ + private boolean isAutoSync; + + /** cron表达式 */ + private String autoSyncCron; + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getProjectDescription() { + return projectDescription; + } + + public void setProjectDescription(String projectDescription) { + this.projectDescription = projectDescription; + } + + public Integer getSubjectId() { + return subjectId; + } + + public void setSubjectId(Integer subjectId) { + this.subjectId = subjectId; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + public String getDatabaseType() { + return databaseType; + } + + public void setDatabaseType(String databaseType) { + this.databaseType = databaseType; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + 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 Map[] getProperties() { + return properties; + } + + public void setProperties(Map[] properties) { + this.properties = properties; + } + + public String[] getIgnoreTableNameRegexArray() { + return ignoreTableNameRegexArray; + } + + public void setIgnoreTableNameRegexArray(String[] ignoreTableNameRegexArray) { + this.ignoreTableNameRegexArray = ignoreTableNameRegexArray; + } + + public String[] getIgnoreColumnNameRegexArray() { + return ignoreColumnNameRegexArray; + } + + public void setIgnoreColumnNameRegexArray(String[] ignoreColumnNameRegexArray) { + this.ignoreColumnNameRegexArray = ignoreColumnNameRegexArray; + } + + public boolean isAutoSync() { + return isAutoSync; + } + + public void setAutoSync(boolean autoSync) { + isAutoSync = autoSync; + } + + public String getAutoSyncCron() { + return autoSyncCron; + } + + public void setAutoSyncCron(String autoSyncCron) { + this.autoSyncCron = autoSyncCron; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DataSourcePropertyMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DataSourcePropertyMapper.java new file mode 100644 index 0000000..2aaa507 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DataSourcePropertyMapper.java @@ -0,0 +1,22 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorDataSourceProperty; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Map; + +/** + * @author zhuff + */ +@Mapper +public interface DataSourcePropertyMapper { + + void addDataSourceProperty(MonitorDataSourceProperty property); + + List> selectPropertyBySourceId(Integer dataSourceId); + + void updateDataSourceProperty(MonitorDataSourceProperty monitorDataSourceProperty); + + void deleteDataSourceProperty(Integer dataSourceId); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseMapper.java new file mode 100644 index 0000000..4bee7c2 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseMapper.java @@ -0,0 +1,25 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorDataSource; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * @author zhuff + */ +@Mapper +public interface DatabaseMapper { + + void addDatabaseSource(MonitorDataSource monitorDataSource); + + MonitorDataSource selectDataSourceById(Integer id); + + void updateDateSource(MonitorDataSource monitorDataSource); + + void deleteDataSource(Integer projectId); + + List selectDataSourceList(); + + MonitorDataSource selectByDatabaseName(String databaseName); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseMetaConverter.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseMetaConverter.java new file mode 100644 index 0000000..c416d68 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseMetaConverter.java @@ -0,0 +1,147 @@ +package com.salpa.subject.mapper; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.salpa.subject.domain.*; +import com.salpa.subject.domain.converter.JsonConverter; +import com.salpa.subject.domain.data.*; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Mapper(componentModel = "spring", uses = JsonConverter.class) +public interface DatabaseMetaConverter { + + default DatabaseMeta of(MonitorDatabaseDocument database, + List tables, + List columns, + List indexes, + List triggers, + List foreignKeys) { + var columnMap = groupBy(columns, MonitorTableColumnDocument::getTableDocumentId); + var indexMap = groupBy(indexes, MonitorTableIndexDocument::getTableDocumentId); + var triggerMap = groupBy(triggers, MonitorTableTriggerDocument::getTableDocumentId); + var fkMap = groupBy(foreignKeys, MonitorTableForeignKeyDocument::getTableDocumentId); + return of(database, tables, columnMap, indexMap, triggerMap, fkMap); + } + + default DatabaseMeta of(MonitorDatabaseDocument database, + List tables, + Map> columnGroupByTableId, + Map> indexGroupByTableId, + Map> triggerGroupByTableId, + Map> fkGroupByTableId) { + List tableMetas = tables.stream() + .map(table -> { + Integer id = table.getId(); + var columns = columnGroupByTableId.getOrDefault(id, Collections.emptyList()); + var indexes = indexGroupByTableId.getOrDefault(id, Collections.emptyList()); + var triggers = triggerGroupByTableId.getOrDefault(id, Collections.emptyList()); + var foreignKeys = fkGroupByTableId.getOrDefault(id, Collections.emptyList()); + return of(table, columns, indexes, triggers, foreignKeys); + }) + .collect(Collectors.toList()); + return of(database, tableMetas); + } + + default DatabaseMeta of(MonitorDatabaseDocument database, List tables) { + DatabaseMeta databaseMeta = new DatabaseMeta(); + databaseMeta.setTables(tables); + databaseMeta.setDatabaseName(database.getDatabaseName()); + databaseMeta.setSchemaName(database.getSchemaName()); + databaseMeta.setProductVersion(database.getProductVersion()); + databaseMeta.setProductName(database.getProductName()); + return databaseMeta; + } + + default TableMeta of(MonitorTableDocument table, + List columns, + List indexes, + List triggers, + List foreignKeys){ + TableMeta tableMeta = new TableMeta(); + tableMeta.setName(table.getName()); + tableMeta.setType(table.getType()); + tableMeta.setComment(table.getComment() == null ? "" : table.getComment()); + List columns1 = new ArrayList<>(); + for (MonitorTableColumnDocument column : columns) { + columns1.add(of(column)); + } + tableMeta.setColumns(columns1); + List indexes1 = new ArrayList<>(); + for (MonitorTableIndexDocument index : indexes) { + indexes1.add(of(index)); + } + tableMeta.setIndexes(indexes1); + List triggers1 = new ArrayList<>(); + for (MonitorTableTriggerDocument trigger : triggers) { + triggers1.add(of(trigger)); + } + tableMeta.setTriggers(triggers1); + List foreignKeys1 = new ArrayList<>(); + for (MonitorTableForeignKeyDocument foreignKey : foreignKeys) { + foreignKeys1.add(of(foreignKey)); + } + tableMeta.setForeignKeys(foreignKeys1); + return tableMeta; + } + + default ColumnMeta of(MonitorTableColumnDocument pojo) { + ColumnMeta columnMeta = new ColumnMeta(); + columnMeta.setName(pojo.getName()); + columnMeta.setType(pojo.getType()); + columnMeta.setComment(pojo.getComment() == null ? "" : pojo.getComment()); + columnMeta.setAutoIncrement(pojo.getAutoIncrement()); + columnMeta.setDataType(pojo.getDataType()); + columnMeta.setDecimalDigits(pojo.getDecimalDigits()); + columnMeta.setDefaultValue(pojo.getDefaultValue()); + columnMeta.setIsPrimaryKey(pojo.getPrimaryKey()); + columnMeta.setNullable(pojo.getNullable()); + columnMeta.setSize(pojo.getSize()); + return columnMeta; + } + + default IndexMeta of(MonitorTableIndexDocument pojo) { + IndexMeta indexMeta = new IndexMeta(); + indexMeta.setName(pojo.getName()); + indexMeta.setIsUniqueKey(pojo.getUnique()); + indexMeta.setColumnNames(JSONObject.parseObject(pojo.getColumnNameArray(), List.class)); + return indexMeta; + } + + default TriggerMeta of(MonitorTableTriggerDocument pojo) { + TriggerMeta triggerMeta = new TriggerMeta(); + triggerMeta.setName(pojo.getName()); + triggerMeta.setTiming(pojo.getTiming()); + triggerMeta.setStatement(pojo.getStatement()); + triggerMeta.setManipulation(pojo.getManipulation()); + triggerMeta.setCreateAt(pojo.getTriggerCreateAt()); + return triggerMeta; + } + + default ForeignKeyMeta of(MonitorTableForeignKeyDocument pojo) { + ForeignKeyMeta foreignKeyMeta = new ForeignKeyMeta(); + foreignKeyMeta.setKeySeq(pojo.getKeySeq()); + foreignKeyMeta.setFkName(pojo.getFkName()); + foreignKeyMeta.setFkTableName(pojo.getFkTableName()); + foreignKeyMeta.setFkColumnName(pojo.getFkColumnName()); + foreignKeyMeta.setPkName(pojo.getPkName()); + foreignKeyMeta.setPkTableName(pojo.getPkTableName()); + foreignKeyMeta.setPkColumnName(pojo.getPkColumnName()); + foreignKeyMeta.setDeleteRule(pojo.getDeleteRule()); + foreignKeyMeta.setUpdateRule(pojo.getUpdateRule()); + return foreignKeyMeta; + } + + List of(List table); + + TableMeta of(TableDocumentResponse table); + + default Map> groupBy(List content, Function idMapping) { + return content.stream() + .collect(Collectors.groupingBy(idMapping)); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseTypeMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseTypeMapper.java new file mode 100644 index 0000000..fabde1d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DatabaseTypeMapper.java @@ -0,0 +1,31 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorDatabaseType; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * @author zhuff + */ +@Mapper +public interface DatabaseTypeMapper { + + List getDatabaseTypeAll(); + + List getList(String databaseType); + + int checkDatabaseTypeUnique(String databaseType); + + int addDatabaseType(MonitorDatabaseType monitorDatabaseType); + + MonitorDatabaseType getById(Integer id); + + int updateDatabaseType(MonitorDatabaseType monitorDatabaseType); + + int deleteDatabaseType(Integer id); + + int countDataSource(String databaseType); + + MonitorDatabaseType getTypeByName(String databaseType); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDescriptionMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDescriptionMapper.java new file mode 100644 index 0000000..9f6be9e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDescriptionMapper.java @@ -0,0 +1,22 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorDocumentDescription; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface DocumentDescriptionMapper { + + List> selectDescriptionByTableName(@Param("projectId") Integer projectId, @Param("name") String name); + + List> selectById(Integer projectId); + + int updateDescription(MonitorDocumentDescription monitorDocumentDescription); + + int exists(MonitorDocumentDescription monitorDocumentDescription); + + int addDescription(MonitorDocumentDescription monitorDocumentDescription); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDiffConverter.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDiffConverter.java new file mode 100644 index 0000000..79503b7 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDiffConverter.java @@ -0,0 +1,163 @@ +package com.salpa.subject.mapper; + +import com.alibaba.fastjson2.JSON; +import com.salpa.subject.domain.*; +import com.salpa.subject.domain.diff.*; +import com.salpa.subject.domain.diff.data.DiffType; +import net.sourceforge.plantuml.json.JsonObject; +import org.mapstruct.Mapper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Mapper(componentModel = "spring") +public interface DocumentDiffConverter { + + default DatabaseDocDiff of(MonitorDatabaseDocument databaseDocument, + List tables, + List columns, + List indexes, + List triggers, + List foreignKeys) { + List tableDocDiffList = of(tables, columns, indexes, triggers, foreignKeys); + return of(databaseDocument, tableDocDiffList); + } + + default List of(List tables, + List columns, + List indexes, + List triggers, + List foreignKeys) { + var columnGroupByTableId = groupBy(columns, MonitorTableColumnDocument::getTableDocumentId); + var indexGroupByTableId = groupBy(indexes, MonitorTableIndexDocument::getTableDocumentId); + var triggerGroupByTableId = groupBy(triggers, MonitorTableTriggerDocument::getTableDocumentId); + var fkGroupByTableId = groupBy(foreignKeys, MonitorTableForeignKeyDocument::getTableDocumentId); + return tables.stream() + .map(table -> { + Integer id = table.getId(); + var tableCols = columnGroupByTableId.getOrDefault(id, Collections.emptyList()); + var tableIndexes = indexGroupByTableId.getOrDefault(id, Collections.emptyList()); + var tableTriggers = triggerGroupByTableId.getOrDefault(id, Collections.emptyList()); + var tableForeignKeys = fkGroupByTableId.getOrDefault(id, Collections.emptyList()); + return of(table, tableCols, tableIndexes, tableTriggers, tableForeignKeys); + }) + .collect(Collectors.toList()); + } + + default DatabaseDocDiff of(MonitorDatabaseDocument databaseDocument, List tables) { + DatabaseDocDiff databaseDocDiff = new DatabaseDocDiff(); + databaseDocDiff.setTables(tables); + databaseDocDiff.setId(databaseDocument.getId()); + databaseDocDiff.setProjectId(databaseDocument.getProjectId()); + databaseDocDiff.setProductVersion(databaseDocument.getProductVersion()); + databaseDocDiff.setProductName(databaseDocument.getProductName()); + databaseDocDiff.setDatabaseName(databaseDocument.getDatabaseName()); + databaseDocDiff.setSchemaName(databaseDocument.getSchemaName()); + databaseDocDiff.setVersion(databaseDocument.getDocumentVersion()); + databaseDocDiff.setCreateAt(databaseDocument.getCreateAt()); + databaseDocDiff.setUpdateAt(databaseDocument.getUpdateAt()); + return databaseDocDiff; + } + + default TableDocDiff of(MonitorTableDocument table, + List columns, + List indexes, + List triggers, + List foreignKeys) { + /*column*/ + List columns1 = new ArrayList<>(); + for (MonitorTableColumnDocument column : columns) { + ColumnDocDiff columnDocDiff = new ColumnDocDiff(); + columnDocDiff.setId(column.getId()); + columnDocDiff.setType(column.getType()); + columnDocDiff.setTableDocumentId(column.getTableDocumentId()); + columnDocDiff.setSize(column.getSize()); + columnDocDiff.setNullable(column.getNullable()); + columnDocDiff.setName(column.getName()); + columnDocDiff.setIsPrimaryKey(column.getPrimaryKey()); + columnDocDiff.setDefaultValue(column.getDefaultValue()); + columnDocDiff.setDecimalDigits(column.getDecimalDigits()); + columnDocDiff.setDataType(column.getDataType()); + columnDocDiff.setDatabaseDocumentId(column.getDatabaseDocumentId()); + columnDocDiff.setComment(column.getComment()); + columnDocDiff.setAutoIncrement(column.getAutoIncrement()); + columnDocDiff.setCreateAt(column.getCreateAt()); + columns1.add(columnDocDiff); + } + /*index*/ + List indexes1 = new ArrayList<>(); + for (MonitorTableIndexDocument index : indexes) { + IndexDocDiff indexDocDiff = new IndexDocDiff(); + indexDocDiff.setId(index.getId()); + indexDocDiff.setName(index.getName()); + indexDocDiff.setTableDocumentId(index.getTableDocumentId()); + indexDocDiff.setDatabaseDocumentId(index.getDatabaseDocumentId()); + indexDocDiff.setIsUnique(index.getUnique()); + indexDocDiff.setColumnNameArray(index.getColumnNameArray()); + indexDocDiff.setCreateAt(index.getCreateAt()); + indexes1.add(indexDocDiff); + } + /*triggers*/ + List triggers1 = new ArrayList<>(); + for (MonitorTableTriggerDocument trigger : triggers) { + TriggerDocDiff triggerDocDiff = new TriggerDocDiff(); + triggerDocDiff.setId(trigger.getId()); + triggerDocDiff.setName(trigger.getName()); + triggerDocDiff.setTableDocumentId(trigger.getTableDocumentId()); + triggerDocDiff.setDatabaseDocumentId(trigger.getDatabaseDocumentId()); + triggerDocDiff.setTiming(trigger.getTiming()); + triggerDocDiff.setManipulation(trigger.getManipulation()); + triggerDocDiff.setStatement(trigger.getStatement()); + triggerDocDiff.setTriggerCreateAt(trigger.getTriggerCreateAt()); + triggerDocDiff.setCreateAt(trigger.getCreateAt()); + triggers1.add(triggerDocDiff); + } + /*foreignKey*/ + List foreignKeys1 = new ArrayList<>(); + for (MonitorTableForeignKeyDocument foreignKey : foreignKeys) { + ForeignKeyDocDiff foreignKeyDocDiff = new ForeignKeyDocDiff(); + foreignKeyDocDiff.setId(foreignKey.getId()); + foreignKeyDocDiff.setKeySeq(foreignKey.getKeySeq()); + foreignKeyDocDiff.setFkName(foreignKey.getFkName()); + foreignKeyDocDiff.setFkTableName(foreignKey.getFkTableName()); + foreignKeyDocDiff.setFkColumnName(foreignKey.getFkColumnName()); + foreignKeyDocDiff.setPkName(foreignKey.getPkName()); + foreignKeyDocDiff.setPkTableName(foreignKey.getPkTableName()); + foreignKeyDocDiff.setPkColumnName(foreignKey.getPkColumnName()); + foreignKeyDocDiff.setTableDocumentId(foreignKey.getTableDocumentId()); + foreignKeyDocDiff.setDatabaseDocumentId(foreignKey.getDatabaseDocumentId()); + foreignKeyDocDiff.setDeleteRule(foreignKey.getDeleteRule()); + foreignKeyDocDiff.setUpdateRule(foreignKey.getUpdateRule()); + foreignKeyDocDiff.setCreateAt(foreignKey.getCreateAt()); + foreignKeys1.add(foreignKeyDocDiff); + } + return TableDocDiff.builder() + .id(table.getId()) + .name(table.getName()) + .comment(table.getComment()) + .type(table.getType()) + .createAt(table.getCreateAt()) + .columns(columns1) + .indexes(indexes1) + .triggers(triggers1) + .foreignKeys(foreignKeys1) + .build(); + } + + ColumnDocDiff of(MonitorTableColumnDocument pojo); + + IndexDocDiff of(MonitorTableIndexDocument pojo); + + TriggerDocDiff of(MonitorTableTriggerDocument pojo); + + ForeignKeyDocDiff of(MonitorTableForeignKeyDocument pojo); + + default Map> groupBy(List content, Function idMapping) { + return content.stream() + .collect(Collectors.groupingBy(idMapping)); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDiscussionMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDiscussionMapper.java new file mode 100644 index 0000000..1d48d12 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentDiscussionMapper.java @@ -0,0 +1,22 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorDocumentDiscussion; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface DocumentDiscussionMapper { + + List> countDiscussionByTableName(@Param("projectId") Integer projectId, @Param("name") String name); + + List> discussionByProjectId(Integer projectId); + + int add(MonitorDocumentDiscussion monitorDocumentDiscussion); + + List getList(MonitorDocumentDiscussion monitorDocumentDiscussion); + + int deleteComment(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentMapper.java new file mode 100644 index 0000000..a41b781 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentMapper.java @@ -0,0 +1,65 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorDatabaseDocument; +import com.salpa.subject.domain.MonitorProjectSyncTask; +import com.salpa.subject.domain.MonitorTableDocument; +import com.salpa.subject.domain.data.DatabaseDocumentResponse; +import com.salpa.subject.domain.data.TableDocumentResponse; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Mapper +public interface DocumentMapper { + + int createSyncTask(MonitorProjectSyncTask projectSyncTask); + + List> getVersionsByProjectId(Integer projectId); + + Long getMaxVersion(Integer projectId); + + MonitorDatabaseDocument getSimpleByProjectId(@Param("projectId") Integer projectId, @Param("version") Long version); + + List getTableDocument(@Param("id") Integer id, @Param("search") String search); + + List> getTableDocumentById(@Param("projectId") Integer id, @Param("search")String search); + + Map getTable(Integer id); + + List getSyncTaskList(@Param("list") Integer[] taskIds); + + int updateStatusAndResultById(Integer taskId); + + List getTableByIds(@Param("list") Integer[] tableIds); + + DatabaseDocumentResponse getByProjectId(@Param("projectId")Integer projectId, @Param("version") Long version); + + List getTableDetails(Integer databaseDocumentId); + + Optional selectNotArchivedByProjectId(Integer projectId); + + void updateIsArchiveById(@Param("id") Integer id, @Param("archive") boolean archive); + + Integer addDatabaseDocument(MonitorDatabaseDocument dbDocPojo); + + Integer insertTableDocumentAndReturnId(MonitorTableDocument tableMeta); + + MonitorTableDocument getTableById(Integer id); + + void updateSyncTask(int syncTask); + + String selectStatusById(Integer taskId); + + Long getDocumentVersionById(Integer docId); + + void deleteDocumentById(Integer docId); + + void updateIsArchive(@Param("projectId") Integer projectId, @Param("version") Long version); + + String getSyncTaskStatus(Integer taskId); + + Integer uniqueDocumentByProjectId(Integer projectId); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentPojoConverter.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentPojoConverter.java new file mode 100644 index 0000000..4cd9142 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentPojoConverter.java @@ -0,0 +1,123 @@ +package com.salpa.subject.mapper; + +import com.alibaba.fastjson2.JSON; +import com.salpa.subject.domain.*; +import com.salpa.subject.domain.converter.JsonConverter; +import com.salpa.subject.domain.data.*; +import net.sourceforge.plantuml.json.JsonObject; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN) +public interface DocumentPojoConverter { + + @Mapping(target = "databaseName", source = "meta.databaseName") + @Mapping(target = "schemaName", source = "meta.schemaName") + @Mapping(target = "isArchive", constant = "false") + MonitorDatabaseDocument toDatabasePojo(Integer projectId, + DatabaseMeta meta, + Long version); + + MonitorTableDocument toTablePojo(Integer databaseDocumentId, + TableMeta meta); + + default List toColumnPojo(Integer databaseDocumentId, + Integer tableDocumentId, + List metaList) { + return metaList.stream() + .map(meta -> toColumnPojo(databaseDocumentId, tableDocumentId, meta)) + .collect(Collectors.toList()); + } + + default MonitorTableColumnDocument toColumnPojo(Integer databaseDocumentId, + Integer tableDocumentId, + ColumnMeta meta) { + MonitorTableColumnDocument monitorTableColumnDocument = new MonitorTableColumnDocument(); + monitorTableColumnDocument.setName(meta.getName()); + monitorTableColumnDocument.setType(meta.getType()); + monitorTableColumnDocument.setComment(meta.getComment()); + monitorTableColumnDocument.setAutoIncrement(meta.getAutoIncrement()); + monitorTableColumnDocument.setDataType(meta.getDataType()); + monitorTableColumnDocument.setDecimalDigits(meta.getDecimalDigits()); + monitorTableColumnDocument.setDefaultValue(meta.getDefaultValue()); + monitorTableColumnDocument.setPrimaryKey(meta.getIsPrimaryKey()); + monitorTableColumnDocument.setNullable(meta.getNullable()); + monitorTableColumnDocument.setSize(meta.getSize()); + monitorTableColumnDocument.setDatabaseDocumentId(databaseDocumentId); + monitorTableColumnDocument.setTableDocumentId(tableDocumentId); + return monitorTableColumnDocument; + } + + default List toIndexPojo(Integer databaseDocumentId, + Integer tableDocumentId, + List metaList) { + return metaList.stream() + .map(meta -> toIndexPojo(databaseDocumentId, tableDocumentId, meta)) + .collect(Collectors.toList()); + } + + default MonitorTableIndexDocument toIndexPojo(Integer databaseDocumentId, + Integer tableDocumentId, + IndexMeta meta) { + MonitorTableIndexDocument monitorTableIndexDocument = new MonitorTableIndexDocument(); + monitorTableIndexDocument.setName(meta.getName()); + monitorTableIndexDocument.setUnique(meta.getIsUniqueKey()); + monitorTableIndexDocument.setColumnNameArray(JSON.toJSONString(meta.getColumnNames())); + monitorTableIndexDocument.setDatabaseDocumentId(databaseDocumentId); + monitorTableIndexDocument.setTableDocumentId(tableDocumentId); + return monitorTableIndexDocument; + } + + default List toTriggerPojo(Integer databaseDocumentId, + Integer tableDocumentId, + List metaList) { + return metaList.stream() + .map(meta -> toTriggerPojo(databaseDocumentId, tableDocumentId, meta)) + .collect(Collectors.toList()); + } + + default MonitorTableTriggerDocument toTriggerPojo(Integer databaseDocumentId, + Integer tableDocumentId, + TriggerMeta meta) { + MonitorTableTriggerDocument monitorTableTriggerDocument = new MonitorTableTriggerDocument(); + monitorTableTriggerDocument.setName(meta.getName()); + monitorTableTriggerDocument.setTiming(meta.getTiming()); + monitorTableTriggerDocument.setStatement(meta.getStatement()); + monitorTableTriggerDocument.setManipulation(meta.getManipulation()); + monitorTableTriggerDocument.setTriggerCreateAt(meta.getCreateAt()); + monitorTableTriggerDocument.setDatabaseDocumentId(databaseDocumentId); + monitorTableTriggerDocument.setTableDocumentId(tableDocumentId); + return monitorTableTriggerDocument; + } + + default List toForeignKeyPojo(Integer docId, + Integer tableMetaId, + List foreignKeys) { + return foreignKeys.stream() + .map(key -> toForeignKeyPojo(docId, tableMetaId, key)) + .collect(Collectors.toList()); + } + + default MonitorTableForeignKeyDocument toForeignKeyPojo(Integer databaseDocumentId, + Integer tableDocumentId, + ForeignKeyMeta foreignKey) { + MonitorTableForeignKeyDocument monitorTableForeignKeyDocument = new MonitorTableForeignKeyDocument(); + monitorTableForeignKeyDocument.setKeySeq(foreignKey.getKeySeq()); + monitorTableForeignKeyDocument.setFkName(foreignKey.getFkName()); + monitorTableForeignKeyDocument.setFkTableName(foreignKey.getFkTableName()); + monitorTableForeignKeyDocument.setFkColumnName(foreignKey.getFkColumnName()); + monitorTableForeignKeyDocument.setPkName(foreignKey.getPkName()); + monitorTableForeignKeyDocument.setPkTableName(foreignKey.getPkTableName()); + monitorTableForeignKeyDocument.setPkColumnName(foreignKey.getPkColumnName()); + monitorTableForeignKeyDocument.setDeleteRule(foreignKey.getDeleteRule()); + monitorTableForeignKeyDocument.setUpdateRule(foreignKey.getUpdateRule()); + monitorTableForeignKeyDocument.setDatabaseDocumentId(databaseDocumentId); + monitorTableForeignKeyDocument.setTableDocumentId(tableDocumentId); + return monitorTableForeignKeyDocument; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentResponseConverter.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentResponseConverter.java new file mode 100644 index 0000000..11ef920 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentResponseConverter.java @@ -0,0 +1,315 @@ +package com.salpa.subject.mapper; + +import com.alibaba.fastjson2.JSON; +import com.salpa.subject.domain.*; +import com.salpa.subject.domain.converter.JsonConverter; +import com.salpa.subject.domain.data.DatabaseDocumentResponse; +import com.salpa.subject.domain.data.TableDocumentResponse; +import com.salpa.subject.domain.diff.*; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN) +public interface DocumentResponseConverter { + + @Mapping(target = "columns", source = "columns") + @Mapping(target = "indexes", source = "indexes") + @Mapping(target = "foreignKeys", source = "foreignKeys") + @Mapping(target = "triggers", source = "triggers") + default TableDocumentResponse of(MonitorTableDocument tableDocument, + Map discussionCountMap, + Map descriptionMap, + List columns, + List indexes, + List foreignKeys, + List triggers) { + TableDocumentResponse tableDocumentResponse = new TableDocumentResponse(); + List columns1 = of(columns, descriptionMap, discussionCountMap); + tableDocumentResponse.setId(tableDocument.getId()); + tableDocumentResponse.setDatabaseDocumentId(tableDocument.getDatabaseDocumentId()); + tableDocumentResponse.setName(tableDocument.getName()); + tableDocumentResponse.setType(tableDocument.getType()); + tableDocumentResponse.setComment(tableDocument.getComment()); + tableDocumentResponse.setCreateAt(tableDocument.getCreateAt()); + tableDocumentResponse.setColumns(columns1); + List indexes1 = new ArrayList<>(); + for (MonitorTableIndexDocument index : indexes) { + TableDocumentResponse.IndexDocumentResponse indexDocumentResponse = new TableDocumentResponse.IndexDocumentResponse(); + indexDocumentResponse.setId(index.getId()); + indexDocumentResponse.setName(index.getName()); + indexDocumentResponse.setIsUnique(index.getUnique()); + indexDocumentResponse.setCreateAt(index.getCreateAt()); + indexDocumentResponse.setColumnNames(JSON.parseArray(index.getColumnNameArray(),String.class)); + indexes1.add(indexDocumentResponse); + } + tableDocumentResponse.setIndexes(indexes1); + List foreignKeys1 = new ArrayList<>(); + for (MonitorTableForeignKeyDocument foreignKey : foreignKeys) { + TableDocumentResponse.ForeignKeyDocumentResponse foreignKeyDocumentResponse = new TableDocumentResponse.ForeignKeyDocumentResponse(); + foreignKeyDocumentResponse.setId(foreignKey.getId()); + foreignKeyDocumentResponse.setKeySeq(foreignKey.getKeySeq()); + foreignKeyDocumentResponse.setFkName(foreignKey.getFkName()); + foreignKeyDocumentResponse.setFkTableName(foreignKey.getFkTableName()); + foreignKeyDocumentResponse.setFkColumnName(foreignKey.getFkColumnName()); + foreignKeyDocumentResponse.setPkName(foreignKey.getPkName()); + foreignKeyDocumentResponse.setPkTableName(foreignKey.getPkTableName()); + foreignKeyDocumentResponse.setPkColumnName(foreignKey.getPkColumnName()); + foreignKeyDocumentResponse.setUpdateRule(foreignKey.getUpdateRule()); + foreignKeyDocumentResponse.setDeleteRule(foreignKey.getDeleteRule()); + foreignKeyDocumentResponse.setCreateAt(foreignKey.getCreateAt()); + foreignKeys1.add(foreignKeyDocumentResponse); + } + tableDocumentResponse.setForeignKeys(foreignKeys1); + List triggers1 = new ArrayList<>(); + for (MonitorTableTriggerDocument trigger : triggers) { + TableDocumentResponse.TriggerDocumentResponse triggerDocumentResponse = new TableDocumentResponse.TriggerDocumentResponse(); + triggerDocumentResponse.setId(trigger.getId()); + triggerDocumentResponse.setName(trigger.getName()); + triggerDocumentResponse.setTiming(trigger.getTiming()); + triggerDocumentResponse.setStatement(trigger.getStatement()); + triggerDocumentResponse.setManipulation(trigger.getManipulation()); + triggerDocumentResponse.setTriggerCreateAt(trigger.getTriggerCreateAt()); + triggerDocumentResponse.setCreateAt(trigger.getCreateAt()); + triggers1.add(triggerDocumentResponse); + } + tableDocumentResponse.setTriggers(triggers1); + return tableDocumentResponse; + } + + @Mapping(target = "columns", source = "columns") + @Mapping(target = "indexes", source = "indexes") + @Mapping(target = "foreignKeys", source = "foreignKeys") + @Mapping(target = "triggers", source = "triggers") + @SuppressWarnings("checkstyle:all") + TableDocumentResponse of(MonitorTableDocument tableDocument, + Integer discussionCount, + String description, + List columns, + List indexes, + List foreignKeys, + List triggers); + + default TableDocumentResponse.ColumnDocumentResponse of(MonitorTableColumnDocument pojo, + String description,Integer count) { + TableDocumentResponse.ColumnDocumentResponse columnDocumentResponse = new TableDocumentResponse.ColumnDocumentResponse(); + columnDocumentResponse.setId(pojo.getId()); + columnDocumentResponse.setName(pojo.getName()); + columnDocumentResponse.setType(pojo.getType()); + columnDocumentResponse.setSize(pojo.getSize()); + columnDocumentResponse.setDecimalDigits(pojo.getDecimalDigits()); + columnDocumentResponse.setComment(pojo.getComment()); + columnDocumentResponse.setIsPrimaryKey(pojo.getPrimaryKey()); + columnDocumentResponse.setNullable(pojo.getNullable()); + columnDocumentResponse.setAutoIncrement(pojo.getAutoIncrement()); + columnDocumentResponse.setDefaultValue(pojo.getDefaultValue()); + columnDocumentResponse.setCreateAt(pojo.getCreateAt()); + columnDocumentResponse.setDescription(description); + columnDocumentResponse.setDiscussionCount(count); + return columnDocumentResponse; + } + + default List of( + List columns, + Map descriptionMapByJoinName, + Map discussionCountMapByJoinName) { + return columns.stream() + .map(column -> { + String description = descriptionMapByJoinName.get(column.getName()); + Integer count = discussionCountMapByJoinName.get(column.getName()); + return of(column, description,count); + }) + .collect(Collectors.toList()); + } + + @Mapping(target = "columnNames", source = "columnNameArray") + TableDocumentResponse.IndexDocumentResponse of(MonitorTableIndexDocument indexDocument); + + @Mapping(target = "id", source = "databaseDocument.id") + @Mapping(target = "createAt", source = "databaseDocument.createAt") + @Mapping(target = "documentVersion", source = "databaseDocument.version") + DatabaseDocumentResponse of(MonitorDatabaseDocument databaseDocument, + List tables); + + default TableDocumentResponse ofDiff(TableDocDiff table, + Map discussionCountMap, + Map descriptionMap) { + List cols = toColumns(table, descriptionMap, discussionCountMap); + List inds = toIndexes(table); + List tris = toTriggers(table); + List fors = toForeignKeys(table); + return ofDiff(table, cols, inds, tris, fors, discussionCountMap, descriptionMap); + } + + default TableDocumentResponse ofDiff(TableDocDiff table, + List cols, + List inds, + List tris, + List fors, + Map discussionCountMap, + Map descriptionMap) { + TableDocumentResponse tableDocumentResponse = new TableDocumentResponse(); + tableDocumentResponse.setId(table.getId()); + tableDocumentResponse.setName(table.getName()); + tableDocumentResponse.setType(table.getType()); + tableDocumentResponse.setComment(table.getComment()); + tableDocumentResponse.setCreateAt(table.getCreateAt()); + tableDocumentResponse.setDiffType(table.getDiffType()); + for (TableDocumentResponse.ColumnDocumentResponse col : cols) { + col.setDescription(descriptionMap.get(col.getName())); + col.setDiscussionCount(discussionCountMap.get(col.getName())); + } + tableDocumentResponse.setColumns(cols); + tableDocumentResponse.setIndexes(inds); + tableDocumentResponse.setTriggers(tris); + tableDocumentResponse.setForeignKeys(fors); + return tableDocumentResponse; + } + + default List toColumns(TableDocDiff table, + Map descriptionMap, + Map discussionCountMap) { + return table.getColumns() + .stream() + .map(col -> toColumn(col, descriptionMap, discussionCountMap)) + .collect(Collectors.toList()); + } + + default List toIndexes(TableDocDiff table) { + return table.getIndexes() + .stream() + .map(ind -> toIndex(ind)) + .collect(Collectors.toList()); + } + + default List toTriggers(TableDocDiff table) { + return table.getTriggers() + .stream() + .map(tri -> toTrigger(tri)) + .collect(Collectors.toList()); + } + + default List toForeignKeys(TableDocDiff table) { + return table.getForeignKeys() + .stream() + .map(foreign -> toForeignKey(foreign)) + .collect(Collectors.toList()); + } + + @Mapping(target = "description", expression = "java(descriptionMap.get(tableName+\".\"+diff.getName()))") + @Mapping(target = "original", + expression = "java(toOriginalColumn(tableName, diff.getOriginal(), descriptionMap))") + default TableDocumentResponse.ColumnDocumentResponse toColumn(ColumnDocDiff diff, + Map descriptionMap, + Map discussionCountMap) { + TableDocumentResponse.ColumnDocumentResponse columnDocumentResponse = new TableDocumentResponse.ColumnDocumentResponse(); + columnDocumentResponse.setId(diff.getId()); + columnDocumentResponse.setName(diff.getName()); + columnDocumentResponse.setType(diff.getType()); + columnDocumentResponse.setSize(diff.getSize()); + columnDocumentResponse.setDecimalDigits(diff.getDecimalDigits()); + columnDocumentResponse.setComment(diff.getComment()); + columnDocumentResponse.setIsPrimaryKey(diff.getIsPrimaryKey()); + columnDocumentResponse.setNullable(diff.getNullable()); + columnDocumentResponse.setAutoIncrement(diff.getAutoIncrement()); + columnDocumentResponse.setDefaultValue(diff.getDefaultValue()); + columnDocumentResponse.setCreateAt(diff.getCreateAt()); + columnDocumentResponse.setDescription(descriptionMap.get(diff.getName())); + columnDocumentResponse.setDiscussionCount(discussionCountMap.get(diff.getName())); + columnDocumentResponse.setDiffType(diff.getDiffType()); + columnDocumentResponse.setOriginal(toOriginalColumn(diff.getOriginal(),descriptionMap,discussionCountMap)); + return columnDocumentResponse; + } + + default TableDocumentResponse.IndexDocumentResponse toIndex(IndexDocDiff diff) { + if (diff == null) { + return null; + } else { + TableDocumentResponse.IndexDocumentResponse indexDocumentResponse = new TableDocumentResponse.IndexDocumentResponse(); + indexDocumentResponse.setId(diff.getId()); + indexDocumentResponse.setName(diff.getName()); + indexDocumentResponse.setIsUnique(diff.getIsUnique()); + indexDocumentResponse.setDiffType(diff.getDiffType()); + indexDocumentResponse.setColumnNames(JSON.parseObject(diff.getColumnNameArray(),List.class)); + indexDocumentResponse.setCreateAt(diff.getCreateAt()); + indexDocumentResponse.setOriginal(toIndex(diff.getOriginal())); + return indexDocumentResponse; + } + } + + default TableDocumentResponse.TriggerDocumentResponse toTrigger(TriggerDocDiff diff) { + if (diff == null) { + return null; + } else { + TableDocumentResponse.TriggerDocumentResponse triggerDocumentResponse = new TableDocumentResponse.TriggerDocumentResponse(); + triggerDocumentResponse.setId(diff.getId()); + triggerDocumentResponse.setName(diff.getName()); + triggerDocumentResponse.setTiming(diff.getTiming()); + triggerDocumentResponse.setDiffType(diff.getDiffType()); + triggerDocumentResponse.setStatement(diff.getStatement()); + triggerDocumentResponse.setManipulation(diff.getManipulation()); + triggerDocumentResponse.setTriggerCreateAt(diff.getTriggerCreateAt()); + triggerDocumentResponse.setCreateAt(diff.getCreateAt()); + triggerDocumentResponse.setOriginal(toTrigger(diff.getOriginal())); + return triggerDocumentResponse; + } + } + + default TableDocumentResponse.ForeignKeyDocumentResponse toForeignKey(ForeignKeyDocDiff diff) { + if (diff == null) { + return null; + } else { + TableDocumentResponse.ForeignKeyDocumentResponse foreignKeyDocumentResponse = new TableDocumentResponse.ForeignKeyDocumentResponse(); + foreignKeyDocumentResponse.setId(diff.getId()); + foreignKeyDocumentResponse.setKeySeq(diff.getKeySeq()); + foreignKeyDocumentResponse.setCreateAt(diff.getCreateAt()); + foreignKeyDocumentResponse.setDiffType(diff.getDiffType()); + foreignKeyDocumentResponse.setFkName(diff.getFkName()); + foreignKeyDocumentResponse.setFkTableName(diff.getFkTableName()); + foreignKeyDocumentResponse.setFkColumnName(diff.getFkColumnName()); + foreignKeyDocumentResponse.setPkName(diff.getPkName()); + foreignKeyDocumentResponse.setPkTableName(diff.getPkTableName()); + foreignKeyDocumentResponse.setPkColumnName(diff.getPkColumnName()); + foreignKeyDocumentResponse.setDeleteRule(diff.getDeleteRule()); + foreignKeyDocumentResponse.setUpdateRule(diff.getUpdateRule()); + foreignKeyDocumentResponse.setCreateAt(diff.getCreateAt()); + foreignKeyDocumentResponse.setOriginal(toForeignKey(diff.getOriginal())); + return foreignKeyDocumentResponse; + } + } + + default TableDocumentResponse.ColumnDocumentResponse toOriginalColumn(ColumnDocDiff diff, + Map descriptionMap, + Map discussionCountMap) { + if (diff == null) { + return null; + } + return toColumn(diff, descriptionMap,discussionCountMap); + } + + default TableDocumentResponse.IndexDocumentResponse toOriginalIndex(IndexDocDiff diff) { + if (diff == null) { + return null; + } + return toIndex(diff); + } + + default TableDocumentResponse.TriggerDocumentResponse toOriginalTrigger(TriggerDocDiff diff) { + if (diff == null) { + return null; + } + return toTrigger(diff); + } + + default TableDocumentResponse.ForeignKeyDocumentResponse toOriginalForeignKey(ForeignKeyDocDiff diff) { + if (diff == null) { + return null; + } + return toForeignKey(diff); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentSimpleResponseConverter.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentSimpleResponseConverter.java new file mode 100644 index 0000000..76882fb --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentSimpleResponseConverter.java @@ -0,0 +1,94 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorDatabaseDocument; +import com.salpa.subject.domain.MonitorTableDocument; +import com.salpa.subject.domain.converter.JsonConverter; +import com.salpa.subject.domain.data.DatabaseDocumentSimpleResponse; +import com.salpa.subject.domain.diff.TableDocDiff; +import com.salpa.subject.domain.diff.data.DiffType; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN) +public interface DocumentSimpleResponseConverter { + + @Mapping(target = "id", source = "database.id") + @Mapping(target = "createAt", source = "database.createAt") + @Mapping(target = "documentVersion", source = "database.version") + DatabaseDocumentSimpleResponse of(MonitorDatabaseDocument database, List tables, String projectName); + + default DatabaseDocumentSimpleResponse of(MonitorDatabaseDocument databaseDocument, + List tables, + DiffType diffType, + String projectName) { + DatabaseDocumentSimpleResponse databaseDocumentSimpleResponse = new DatabaseDocumentSimpleResponse(); + databaseDocumentSimpleResponse.setTables(tables); + databaseDocumentSimpleResponse.setDiffType(diffType); + databaseDocumentSimpleResponse.setProductName(databaseDocument.getProductName()); + databaseDocumentSimpleResponse.setId(databaseDocument.getId()); + databaseDocumentSimpleResponse.setSchemaName(databaseDocument.getSchemaName()); + databaseDocumentSimpleResponse.setDatabaseName(databaseDocument.getDatabaseName()); + databaseDocumentSimpleResponse.setProjectName(projectName); + databaseDocumentSimpleResponse.setProductVersion(databaseDocument.getProductVersion()); + databaseDocumentSimpleResponse.setDocumentVersion(databaseDocument.getDocumentVersion()); + databaseDocumentSimpleResponse.setCreateAt(databaseDocument.getCreateAt()); + return databaseDocumentSimpleResponse; + } + + default DatabaseDocumentSimpleResponse.TableData of(MonitorTableDocument tables, + Integer discussionCount, + String description) { + DatabaseDocumentSimpleResponse.TableData tableData = new DatabaseDocumentSimpleResponse.TableData(); + tableData.setId(tables.getId()); + tableData.setName(tables.getName()); + tableData.setComment(tables.getComment()); + tableData.setType(tables.getType()); + tableData.setDescription(description); + tableData.setDiscussionCount(discussionCount); + return tableData; + } + + default List of(List tables, + Map discussionCountMapByTableName, + Map descriptionMapByTableName) { + return tables.stream() + .map(table -> { + Integer count = discussionCountMapByTableName.get(table.getName()); + String description = descriptionMapByTableName.get(table.getName()); + return of(table, count, description); + }) + .collect(Collectors.toList()); + } + + default List ofDiff(List tables, + Map discussionCountMapByTableName, + Map descriptionMapByTableName) { + return tables.stream() + .map(tableDocDiff -> { + String tableName = tableDocDiff.getName(); + Integer count = discussionCountMapByTableName.get(tableName); + String description = descriptionMapByTableName.get(tableName); + return ofDiff(tableDocDiff, count, description); + }) + .collect(Collectors.toList()); + } + + default DatabaseDocumentSimpleResponse.TableData ofDiff(TableDocDiff table, + Integer discussionCount, + String description) { + DatabaseDocumentSimpleResponse.TableData tableData = new DatabaseDocumentSimpleResponse.TableData(); + tableData.setDiscussionCount(discussionCount); + tableData.setDescription(description); + tableData.setId(table.getId()); + tableData.setName(table.getName()); + tableData.setType(table.getType()); + tableData.setComment(table.getComment()); + tableData.setDiffType(table.getDiffType()); + return tableData; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentTemplatePropertyMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentTemplatePropertyMapper.java new file mode 100644 index 0000000..54d18b0 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/DocumentTemplatePropertyMapper.java @@ -0,0 +1,12 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorDocumentTemplateProperty; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface DocumentTemplatePropertyMapper { + + List selectAll(); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/OperationLogMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/OperationLogMapper.java new file mode 100644 index 0000000..90e1ab2 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/OperationLogMapper.java @@ -0,0 +1,14 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorOperationLog; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface OperationLogMapper { + + void addOperationLog(MonitorOperationLog monitorOperationLog); + + List getList(Integer projectId); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectMapper.java new file mode 100644 index 0000000..41f42de --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectMapper.java @@ -0,0 +1,29 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorProject; +import com.salpa.subject.domain.vo.MonitorDataSourceVo; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Map; + +/** + * @author zhuff + */ +@Mapper +public interface ProjectMapper { + + List> getList(MonitorDataSourceVo monitorDataSourceVo); + + int checkProjectNameUnique(String projectName); + + int addProject(MonitorProject monitorProject); + + MonitorProject selectById(Integer id); + + int checkDatabaseNameUnique(String databaseName); + + int updateProject(MonitorProject monitorProject); + + int deleteProject(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectSyncRuleMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectSyncRuleMapper.java new file mode 100644 index 0000000..85c272e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectSyncRuleMapper.java @@ -0,0 +1,29 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorProjectSyncRule; +import com.salpa.subject.domain.MonitorProjectSyncTask; +import com.salpa.subject.domain.vo.ProjectSyncRule; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Arrays; +import java.util.List; + +/** + * @author zhuff + */ +@Mapper +public interface ProjectSyncRuleMapper { + + void addProjectSyncRule(@Param("projectSyncRule") ProjectSyncRule monitorProjectSyncRule, @Param("tableName") String ignoreTableNameRegex, @Param("columnName") String ignoreColumnNameRegex); + + MonitorProjectSyncRule selectByProjectId(Integer id); + + void deletProjectSyncRule(Integer id); + + List selectByIsAutoSyncAndNotInProjectIds(@Param("autoSync") boolean autoSync, @Param("list") List projectIds); + + boolean existsByProjectIdAndCron(@Param("projectId") int projectId, @Param("cron") String cron); + + List selectByIsAutoSyncAndProjectIds(@Param("autoSync") boolean autoSync, @Param("list") List projectIds); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectSyncTaskMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectSyncTaskMapper.java new file mode 100644 index 0000000..71bd208 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/ProjectSyncTaskMapper.java @@ -0,0 +1,16 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorProjectSyncTask; +import com.salpa.subject.domain.data.ProjectSyncTaskStatus; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface ProjectSyncTaskMapper { + + void updateStatusAndResultById(@Param("taskId") Integer taskId, @Param("status")String status, @Param("result")String result); + + List listNewTasks(int size); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/SubjectMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/SubjectMapper.java new file mode 100644 index 0000000..18d910d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/SubjectMapper.java @@ -0,0 +1,27 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorSubject; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * @author zhuff + */ +@Mapper +public interface SubjectMapper { + + List getList(String subjectName); + + MonitorSubject getById(Integer id); + + int checkSubjectNameUnique(String subjectName); + + int addSubject(MonitorSubject monitorSubject); + + int updateSubject(MonitorSubject monitorSubject); + + int deleteSubject(Integer id); + + int selectCount(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/TableColumnDocumentMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/TableColumnDocumentMapper.java new file mode 100644 index 0000000..98e1de0 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/TableColumnDocumentMapper.java @@ -0,0 +1,16 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorTableColumnDocument; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface TableColumnDocumentMapper { + + List getTableColumn(Integer id); + + void insertTableColumn(MonitorTableColumnDocument column); + + List getTableColumnByTableId(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/TableForeignKeyDocumentMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/TableForeignKeyDocumentMapper.java new file mode 100644 index 0000000..e532769 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/TableForeignKeyDocumentMapper.java @@ -0,0 +1,16 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorTableForeignKeyDocument; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface TableForeignKeyDocumentMapper { + + List getTableForeignKey(Integer id); + + void insertTableForeignKey(MonitorTableForeignKeyDocument foreignKey); + + List getTableForeignKeyByTableId(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/TableIndexDocumentMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/TableIndexDocumentMapper.java new file mode 100644 index 0000000..9f8ec4a --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/TableIndexDocumentMapper.java @@ -0,0 +1,16 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorTableIndexDocument; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface TableIndexDocumentMapper { + + List getTableIndex(Integer id); + + void insertTableIndex(MonitorTableIndexDocument index); + + List getTableIndexByTableId(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/TableTriggerDocumentMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/TableTriggerDocumentMapper.java new file mode 100644 index 0000000..0597485 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/TableTriggerDocumentMapper.java @@ -0,0 +1,16 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorTableTriggerDocument; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface TableTriggerDocumentMapper { + + List getTableTrigger(Integer id); + + void insertTableTrigger(MonitorTableTriggerDocument trigger); + + List getTableTriggerByTableId(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/mapper/WarningMapper.java b/salpa-system/src/main/java/com/salpa/subject/mapper/WarningMapper.java new file mode 100644 index 0000000..da4c2b1 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/mapper/WarningMapper.java @@ -0,0 +1,32 @@ +package com.salpa.subject.mapper; + +import com.salpa.subject.domain.MonitorWarning; +import org.apache.ibatis.annotations.Param; +import org.mapstruct.Mapper; + +import java.util.List; + +/** + * @author zff + */ +@Mapper +public interface WarningMapper { + /** + * 获取告警列表 + * @return + */ + List warningList(); + + /** + * 修改告警状态 + * @param id + * @return + */ + int updateStatus(@Param("id") Long id, @Param("userName") String userName); + + /** + * 新增告警 + * @param monitorWarning + */ + void addWarning(MonitorWarning monitorWarning); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/DatabaseConnectionFactory.java b/salpa-system/src/main/java/com/salpa/subject/service/DatabaseConnectionFactory.java new file mode 100644 index 0000000..4b3ce7f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/DatabaseConnectionFactory.java @@ -0,0 +1,34 @@ +package com.salpa.subject.service; + +import lombok.Builder; +import lombok.Data; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +public interface DatabaseConnectionFactory { + + boolean support(String databaseType); + + Connection getConnection(Context context) throws SQLException; + + @Builder + @Data + class Context { + + private String databaseType; + + private String username; + + private String password; + + private String url; + + private String databaseName; + + private String schemaName; + + private Properties properties; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/DatabaseTypeService.java b/salpa-system/src/main/java/com/salpa/subject/service/DatabaseTypeService.java new file mode 100644 index 0000000..0b8f2e2 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/DatabaseTypeService.java @@ -0,0 +1,29 @@ +package com.salpa.subject.service; + +import com.salpa.subject.domain.MonitorDatabaseType; + +import java.util.List; + +/** + * @author zhuff + */ +public interface DatabaseTypeService { + + List getDatabaseTypeAll(); + + List getList(String databaseType); + + int addDatabaseType(MonitorDatabaseType monitorDatabaseType); + + String checkDatabaseTypeUnique(String databaseType); + + MonitorDatabaseType getById(Integer id); + + int updateDatabaseType(MonitorDatabaseType monitorDatabaseType); + + int deleteDatabaseType(Integer id); + + String resolveDriverClassName(String jdbcDriverFileUrl); + + String resolveDriverClassNameFromLocal(String jdbcDriverFilePath); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/DocumentDescriptionService.java b/salpa-system/src/main/java/com/salpa/subject/service/DocumentDescriptionService.java new file mode 100644 index 0000000..7a3505d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/DocumentDescriptionService.java @@ -0,0 +1,8 @@ +package com.salpa.subject.service; + +import com.salpa.subject.domain.MonitorDocumentDescription; + +public interface DocumentDescriptionService { + + int updateDescription(MonitorDocumentDescription monitorDocumentDescription); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/DocumentDiscussionService.java b/salpa-system/src/main/java/com/salpa/subject/service/DocumentDiscussionService.java new file mode 100644 index 0000000..29c8842 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/DocumentDiscussionService.java @@ -0,0 +1,13 @@ +package com.salpa.subject.service; + +import com.salpa.subject.domain.MonitorDocumentDiscussion; + +import java.util.List; + +public interface DocumentDiscussionService { + int add(MonitorDocumentDiscussion monitorDocumentDiscussion); + + List getList(MonitorDocumentDiscussion monitorDocumentDiscussion); + + int deleteComment(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/DocumentService.java b/salpa-system/src/main/java/com/salpa/subject/service/DocumentService.java new file mode 100644 index 0000000..3a52b3b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/DocumentService.java @@ -0,0 +1,32 @@ +package com.salpa.subject.service; + +import com.salpa.subject.domain.MonitorProjectSyncTask; +import com.salpa.subject.domain.data.DatabaseDocumentSimpleResponse; +import com.salpa.subject.domain.data.TableDocumentResponse; + +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +public interface DocumentService { + + int createSyncTask(Integer projectId, Long userId, boolean ignoreIfExists); + + List> getVersionsByProjectId(Integer projectId); + + List> getTableDocumentByProjectId(Integer projectId, Long version, String search); + + List getSyncTaskList(Integer[] taskIds); + + int cancelTask(Integer taskId); + + void export(Integer projectId, Long version, String fileType, OutputStream out); + + DatabaseDocumentSimpleResponse getSimpleByProjectId(Integer projectId, Long version, String search, Long originalVersion); + + TableDocumentResponse getTableDetail(Integer id,Integer projectId,Long version,Long originalVersion); + + void updateSyncTask(int syncTask); + + String getSyncTaskStatus(Integer taskId); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/DocumentSyncService.java b/salpa-system/src/main/java/com/salpa/subject/service/DocumentSyncService.java new file mode 100644 index 0000000..bf4e262 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/DocumentSyncService.java @@ -0,0 +1,14 @@ +package com.salpa.subject.service; + +public interface DocumentSyncService { + + Integer syncByProjectId(Integer projectId, Integer taskId); + + String selectStatusById(Integer taskId); + + Long getDocumentVersionById(Integer docId); + + void deleteDocumentById(Integer docId); + + void updateDocument(Integer projectId, Long version); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/DocumentTemplateService.java b/salpa-system/src/main/java/com/salpa/subject/service/DocumentTemplateService.java new file mode 100644 index 0000000..e4cf2c3 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/DocumentTemplateService.java @@ -0,0 +1,10 @@ +package com.salpa.subject.service; + +import com.salpa.subject.domain.data.DocumentTemplatePropertiesResponse; +import org.springframework.stereotype.Service; + +@Service +public interface DocumentTemplateService { + + DocumentTemplatePropertiesResponse getAllProperties(); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/ProjectService.java b/salpa-system/src/main/java/com/salpa/subject/service/ProjectService.java new file mode 100644 index 0000000..996dce6 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/ProjectService.java @@ -0,0 +1,41 @@ +package com.salpa.subject.service; + +import com.salpa.subject.domain.MonitorDataSource; +import com.salpa.subject.domain.MonitorOperationLog; +import com.salpa.subject.domain.MonitorProject; +import com.salpa.subject.domain.vo.MonitorDataSourceVo; +import com.salpa.subject.domain.vo.OperationLogVo; + +import java.sql.Connection; +import java.util.List; +import java.util.Map; + +/** + * @author zhuff + */ +public interface ProjectService { + + List> getList(MonitorDataSourceVo monitorDataSourceVo); + + String checkProjectNameUnique(String projectName); + + int addProject(MonitorProject monitorProject); + + MonitorProject getProjectById(Integer id); + + String checkDatabaseNameUnique(String databaseName); + + MonitorProject getById(Integer id); + + MonitorDataSource getDataSource(Integer id); + + int updateProject(MonitorProject monitorProject); + + int deleteProject(Integer id); + + int testConnection(MonitorDataSource monitorDataSource); + + List getLogList(OperationLogVo operationLogVo); + + Connection create(MonitorDataSource dataSource, List> properties); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/SubjectService.java b/salpa-system/src/main/java/com/salpa/subject/service/SubjectService.java new file mode 100644 index 0000000..71d7459 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/SubjectService.java @@ -0,0 +1,23 @@ +package com.salpa.subject.service; + +import com.salpa.subject.domain.MonitorSubject; + +import java.util.List; + +/** + * @author zhuff + */ +public interface SubjectService { + + List getList(Integer pageNum,Integer pageSize,String subjectName); + + MonitorSubject getById(Integer id); + + String checkSubjectNameUnique(String subjectName); + + int addSubject(MonitorSubject monitorSubject); + + int updateSubject(MonitorSubject monitorSubject); + + int deleteSubject(Integer id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/WarningService.java b/salpa-system/src/main/java/com/salpa/subject/service/WarningService.java new file mode 100644 index 0000000..1b322b4 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/WarningService.java @@ -0,0 +1,26 @@ +package com.salpa.subject.service; + +import com.salpa.subject.domain.MonitorWarning; + +import java.util.List; + +/** + * @author zff + */ +public interface WarningService { + + /** + * 获取告警列表 + * @param pageNum + * @param pageSize + * @return + */ + List warningList(Integer pageNum, Integer pageSize); + + /** + * 修改告警状态 + * @param id + * @return + */ + int updateStatus(Long id); +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/DatabaseTypeServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/DatabaseTypeServiceImpl.java new file mode 100644 index 0000000..6fbd8dd --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/DatabaseTypeServiceImpl.java @@ -0,0 +1,195 @@ +package com.salpa.subject.service.impl; + +import com.salpa.common.config.SalpaConfig; +import com.salpa.common.constant.UserConstants; +import com.salpa.subject.domain.MonitorDatabaseType; +import com.salpa.subject.mapper.DatabaseTypeMapper; +import com.salpa.subject.service.DatabaseTypeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +@Service +public class DatabaseTypeServiceImpl implements DatabaseTypeService { + + @Autowired + private DatabaseTypeMapper databaseTypeMapper; + + /** + * 获取数据库类型列表 + * @author zhuff + * @return + */ + @Override + public List getDatabaseTypeAll() { + return databaseTypeMapper.getDatabaseTypeAll(); + } + + /** + * 获取数据库类型列表 + * @author zhuff + * @return + */ + @Override + public List getList(String databaseType) { + List list = databaseTypeMapper.getList(databaseType); + for (MonitorDatabaseType monitorDatabaseType : list) { + int count = databaseTypeMapper.countDataSource(monitorDatabaseType.getDatabaseType()); + } + return list; + } + + /** + * 新增数据库类型 + * @param monitorDatabaseType + * @return + */ + @Override + public int addDatabaseType(MonitorDatabaseType monitorDatabaseType) { + + return databaseTypeMapper.addDatabaseType(monitorDatabaseType); + } + + /** + * 修改数据库类型 + * @param monitorDatabaseType + * @return + */ + @Override + public int updateDatabaseType(MonitorDatabaseType monitorDatabaseType) { + return databaseTypeMapper.updateDatabaseType(monitorDatabaseType); + } + + /** + * 删除数据库类型 + * @param id + * @return + */ + @Override + public int deleteDatabaseType(Integer id) { + return databaseTypeMapper.deleteDatabaseType(id); + } + + /** + * 根据url解析数据库驱动类名 + * @param jdbcDriverFileUrl + * @return + */ + @Override + public String resolveDriverClassName(String jdbcDriverFileUrl) { + String className = ""; + /*下载驱动*/ + try { + URL url = new URL(jdbcDriverFileUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + // 设置超时间为3秒 + conn.setConnectTimeout(3 * 1000); + // 防止屏蔽程序抓取而返回403错误 + conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); + // 得到输入流 + InputStream inputStream = conn.getInputStream(); + // 获取字节数组 + byte[] getData = readInputStream(inputStream); + // 获取文件名 + String[] split = jdbcDriverFileUrl.split("/"); + String fileName = split[split.length-1]; + // 文件保存位置 + File saveDir = new File(SalpaConfig.getDriverPath()); + if (!saveDir.exists()) { + saveDir.mkdir(); + } else { + saveDir.delete(); + } + File file = new File(saveDir + File.separator + fileName); + FileOutputStream fos = new FileOutputStream(file); + fos.write(getData); + if (fos != null) { + fos.close(); + } + if (inputStream != null) { + inputStream.close(); + } + /*获取类名*/ + JarFile jarFile = new JarFile(saveDir + File.separator + fileName); + ZipEntry entry = jarFile.getEntry("META-INF/services/java.sql.Driver"); + InputStream stream = jarFile.getInputStream(entry); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + className = reader.readLine(); + } catch (Exception e) { + e.printStackTrace(); + } + return className; + } + + /** + * 根据path解析数据库驱动类名 + * @param jdbcDriverFilePath + * @return + */ + @Override + public String resolveDriverClassNameFromLocal(String jdbcDriverFilePath) { + String className = ""; + try { + /*获取类名*/ + JarFile jarFile = new JarFile(SalpaConfig.getProfile()+jdbcDriverFilePath); + ZipEntry entry = jarFile.getEntry("META-INF/services/java.sql.Driver"); + InputStream stream = jarFile.getInputStream(entry); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + className = reader.readLine(); + } catch (Exception e) { + e.printStackTrace(); + } + return className; + } + + + /** + * 从输入流中获取字节数组 + * + * @param inputStream + * @return + * @throws IOException + */ + public static byte[] readInputStream(InputStream inputStream) throws IOException { + byte[] buffer = new byte[1024]; + int len = 0; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + while ((len = inputStream.read(buffer)) != -1) { + bos.write(buffer, 0, len); + } + bos.close(); + return bos.toByteArray(); + } + + /** + * 校验数据库类型是否存在 + * @param databaseType + * @return + */ + @Override + public String checkDatabaseTypeUnique(String databaseType) { + int count = databaseTypeMapper.checkDatabaseTypeUnique(databaseType); + if (count > 0) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + + /** + * 根据id获取数据库类型 + * @param id + * @return + */ + @Override + public MonitorDatabaseType getById(Integer id) { + return databaseTypeMapper.getById(id); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentDescriptionServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentDescriptionServiceImpl.java new file mode 100644 index 0000000..24a4009 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentDescriptionServiceImpl.java @@ -0,0 +1,30 @@ +package com.salpa.subject.service.impl; + + +import com.salpa.subject.domain.MonitorDocumentDescription; +import com.salpa.subject.mapper.DocumentDescriptionMapper; +import com.salpa.subject.service.DocumentDescriptionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DocumentDescriptionServiceImpl implements DocumentDescriptionService { + + @Autowired + private DocumentDescriptionMapper documentDescriptionMapper; + + /** + * 更新描述 + * @param monitorDocumentDescription + * @return + */ + @Override + public int updateDescription(MonitorDocumentDescription monitorDocumentDescription) { + int exists = documentDescriptionMapper.exists(monitorDocumentDescription); + if (exists > 0) { + return documentDescriptionMapper.updateDescription(monitorDocumentDescription); + } else { + return documentDescriptionMapper.addDescription(monitorDocumentDescription); + } + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentDiscussionServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentDiscussionServiceImpl.java new file mode 100644 index 0000000..660f968 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentDiscussionServiceImpl.java @@ -0,0 +1,49 @@ +package com.salpa.subject.service.impl; + +import com.github.pagehelper.PageHelper; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.subject.domain.MonitorDocumentDiscussion; +import com.salpa.subject.mapper.DocumentDiscussionMapper; +import com.salpa.subject.service.DocumentDiscussionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class DocumentDiscussionServiceImpl implements DocumentDiscussionService { + + @Autowired + private DocumentDiscussionMapper documentDiscussionMapper; + + /** + * 新增评论 + * @param monitorDocumentDiscussion + * @return + */ + @Override + public int add(MonitorDocumentDiscussion monitorDocumentDiscussion) { + + return documentDiscussionMapper.add(monitorDocumentDiscussion); + } + /** + * 获取评论列表 + * */ + @Override + public List getList(MonitorDocumentDiscussion monitorDocumentDiscussion) { + PageHelper.startPage(monitorDocumentDiscussion.getPageNum(),monitorDocumentDiscussion.getPageSize()); + List list = documentDiscussionMapper.getList(monitorDocumentDiscussion); + for (MonitorDocumentDiscussion documentDiscussion : list) { + documentDiscussion.setLoginUser(SecurityUtils.getLoginUser().getUser().getUserName()); + } + return list; + } + /** + * 删除评论 + * @author hzl + * @param id + * @return + */ + @Override + public int deleteComment(Integer id) {return documentDiscussionMapper.deleteComment(id);} +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentServiceImpl.java new file mode 100644 index 0000000..8cb2042 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentServiceImpl.java @@ -0,0 +1,442 @@ +package com.salpa.subject.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.salpa.common.core.domain.AjaxResult; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.subject.domain.*; +import com.salpa.subject.domain.data.DatabaseDocumentResponse; +import com.salpa.subject.domain.data.DatabaseDocumentSimpleResponse; +import com.salpa.subject.domain.data.ProjectSyncTaskStatus; +import com.salpa.subject.domain.data.TableDocumentResponse; +import com.salpa.subject.domain.diff.DatabaseDocDiff; +import com.salpa.subject.domain.diff.DiffTypePredictor; +import com.salpa.subject.domain.diff.DocumentDiffChecker; +import com.salpa.subject.domain.diff.TableDocDiff; +import com.salpa.subject.domain.diff.data.DiffType; +import com.salpa.subject.domain.generator.DocumentFileGenerator; +import com.salpa.subject.domain.generator.DocumentFileType; +import com.salpa.subject.mapper.*; +import com.salpa.subject.service.DocumentService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class DocumentServiceImpl implements DocumentService { + + @Autowired + private DocumentMapper documentMapper; + + @Autowired + private ProjectMapper projectMapper; + + @Autowired + private DocumentDescriptionMapper documentDescriptionMapper; + + @Autowired + private DocumentDiscussionMapper documentDiscussionMapper; + + @Autowired + private TableColumnDocumentMapper tableColumnDocumentMapper; + + @Autowired + private TableIndexDocumentMapper tableIndexDocumentMapper; + + @Autowired + private TableForeignKeyDocumentMapper tableForeignKeyDocumentMapper; + + @Autowired + private TableTriggerDocumentMapper tableTriggerDocumentMapper; + + @Autowired + private DocumentDiffConverter documentDiffConverter; + + @Autowired + private DocumentResponseConverter documentResponseConverter; + + @Autowired + private DocumentSimpleResponseConverter documentSimpleResponseConverter; + + @Autowired + private DocumentDiffChecker documentDiffChecker; + + private final List documentFileGenerators; + + @Autowired + private OperationLogMapper operationLogMapper; + + public DocumentServiceImpl(List documentFileGenerators) { + this.documentFileGenerators = documentFileGenerators; + } + + /** + * 创建同步任务 + * @param projectId + * @param userId + * @param ignoreIfExists + * @return + */ + @Override + public int createSyncTask(Integer projectId,Long userId, boolean ignoreIfExists) { + SysUser user = SecurityUtils.getLoginUser().getUser(); + MonitorProjectSyncTask projectSyncTask = new MonitorProjectSyncTask(); + projectSyncTask.setProjectId(projectId); + projectSyncTask.setStatus(ProjectSyncTaskStatus.NEW.toString()); + projectSyncTask.setUserId(user.getUserId()); + documentMapper.createSyncTask(projectSyncTask); + /*添加日志*/ + MonitorOperationLog monitorOperationLog = MonitorOperationLog.builder() + .operatorUserId(user.getUserId()) + .operatorNickname(user.getNickName()) + .operatorUsername(user.getUserName()) + .operatorUserId(user.getUserId()) + .operationName("创建同步任务") + .operationCode("createSyncTask") + .operationModule("project") + .operationResponse(JSON.toJSONString(AjaxResult.success(projectSyncTask.getId()))) + .success(true) + .involvedProjectId(projectId) + .build(); + operationLogMapper.addOperationLog(monitorOperationLog); + return projectSyncTask.getId(); + } + + /** + * 获取版本号 + * @param projectId + * @return + */ + @Override + public List> getVersionsByProjectId(Integer projectId) { + List> list = documentMapper.getVersionsByProjectId(projectId); + for (Map map : list) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String createAt1 = dateFormat.format(map.get("createAt")); + map.put("createAt",createAt1); + } + return list; + } + + /** + * 获取文档信息 + * @param projectId + * @param version + * @return + */ + @Override + public DatabaseDocumentSimpleResponse getSimpleByProjectId(Integer projectId, Long version, String search, Long originalVersion) { + Integer count = documentMapper.uniqueDocumentByProjectId(projectId); + if (count>0) { + String projectName = projectMapper.selectById(projectId).getProjectName(); + MonitorDatabaseDocument monitorDatabaseDocument; + if (version == null) { + version = documentMapper.getMaxVersion(projectId); + monitorDatabaseDocument = documentMapper.getSimpleByProjectId(projectId,version); + } else { + monitorDatabaseDocument = documentMapper.getSimpleByProjectId(projectId,version); + } + List> discussionList = documentDiscussionMapper.discussionByProjectId(projectId); + Map discussionCountMapByTableName = new HashMap<>(); + for (Map map : discussionList) { + discussionCountMapByTableName.put(map.get("tableName").toString(),Integer.parseInt(map.get("count").toString())); + } + List> descriptionList = documentDescriptionMapper.selectById(projectId); + Map descriptionMapByTableName = new HashMap<>(); + for (Map map : descriptionList) { + descriptionMapByTableName.put(map.get("tableName"),map.get("content")); + } + if (originalVersion == null) { + List list = documentMapper.getTableDocument(monitorDatabaseDocument.getId(),search); + var tableMetas = documentSimpleResponseConverter.of( + list, + discussionCountMapByTableName, + descriptionMapByTableName + ); + DatabaseDocumentSimpleResponse databaseDocumentSimpleResponse = documentSimpleResponseConverter.of(monitorDatabaseDocument, tableMetas, DiffType.NONE, projectName); + List tables = databaseDocumentSimpleResponse.getTables(); + for (DatabaseDocumentSimpleResponse.TableData table : tables) { + table.setColumns(tableColumnDocumentMapper.getTableColumnByTableId(table.getId())); + } + return databaseDocumentSimpleResponse; + } else { + var diffResults = tableDiffs(projectId, originalVersion, version); + var tableMetas = documentSimpleResponseConverter.ofDiff( + diffResults, discussionCountMapByTableName, descriptionMapByTableName); + DiffType diffType = DiffTypePredictor.predict(tableMetas); + DatabaseDocumentSimpleResponse documentSimpleResponse = documentSimpleResponseConverter.of(monitorDatabaseDocument, tableMetas, diffType, projectName); + List tables = documentSimpleResponse.getTables(); + for (DatabaseDocumentSimpleResponse.TableData table : tables) { + table.setColumns(tableColumnDocumentMapper.getTableColumnByTableId(table.getId())); + } + return documentSimpleResponse; + } + } else { + return new DatabaseDocumentSimpleResponse(); + } + } + + /** + * 获取表格详情 + * @param id + * @param projectId + * @param version + * @param originalVersion + * @return + */ + @Override + public TableDocumentResponse getTableDetail(Integer id,Integer projectId,Long version,Long originalVersion) { + if (version == null) { + version = documentMapper.getMaxVersion(projectId); + } + if (originalVersion != null) { + List diffResults = + this.tableDiffs(projectId, originalVersion, version); + TableDocDiff diff = TableDocDiff.builder().build(); + for (TableDocDiff diffResult : diffResults) { + if (diffResult.getId().equals(id)) { + diff = diffResult; + } + } + // discussion + List> discussionList = documentDiscussionMapper.countDiscussionByTableName(projectId,diff.getName()); + Map discussionCountMapByJoinName = new HashMap<>(); + for (Map map : discussionList) { + discussionCountMapByJoinName.put(map.get("columnName").toString(),Integer.parseInt(map.get("count").toString())); + } + // description + List> descriptionList = documentDescriptionMapper.selectDescriptionByTableName(projectId,diff.getName()); + Map descriptionMapByJoinName = new HashMap<>(); + for (Map map : descriptionList) { + descriptionMapByJoinName.put(map.get("columnName"),map.get("content")); + } + return documentResponseConverter.ofDiff(diff, discussionCountMapByJoinName, descriptionMapByJoinName); + } else { + MonitorTableDocument monitorTableDocument = documentMapper.getTableById(id); + List columns = tableColumnDocumentMapper.getTableColumnByTableId(monitorTableDocument.getId()); + List indexes = tableIndexDocumentMapper.getTableIndexByTableId(monitorTableDocument.getId()); + List foreignKeys = tableForeignKeyDocumentMapper.getTableForeignKeyByTableId(monitorTableDocument.getId()); + List triggers = tableTriggerDocumentMapper.getTableTriggerByTableId(monitorTableDocument.getId()); + // discussion + List> discussionList = documentDiscussionMapper.countDiscussionByTableName(projectId,monitorTableDocument.getName()); + Map discussionCountMapByJoinName = new HashMap<>(); + for (Map map : discussionList) { + discussionCountMapByJoinName.put(map.get("columnName").toString(),Integer.parseInt(map.get("count").toString())); + } + // description + List> descriptionList = documentDescriptionMapper.selectDescriptionByTableName(projectId,monitorTableDocument.getName()); + Map descriptionMapByJoinName = new HashMap<>(); + for (Map map : descriptionList) { + descriptionMapByJoinName.put(map.get("columnName"),map.get("content")); + } + return documentResponseConverter.of(monitorTableDocument,discussionCountMapByJoinName,descriptionMapByJoinName,columns,indexes,foreignKeys,triggers); + } + } + + /** + * 修改同步任务 + * @param syncTask + */ + @Override + public void updateSyncTask(int syncTask) { + documentMapper.updateSyncTask(syncTask); + } + + /** + * 获取同步任务状态 + * @param taskId + * @return + */ + @Override + public String getSyncTaskStatus(Integer taskId) { + return documentMapper.getSyncTaskStatus(taskId); + } + + /** + * 版本对比 + * @param projectId + * @param originalVersion + * @param currentVersion + * @return + */ + public List tableDiffs(Integer projectId, Long originalVersion, Long currentVersion) { + var original = documentMapper.getSimpleByProjectId(projectId, originalVersion); + MonitorDatabaseDocument current = documentMapper.getSimpleByProjectId(projectId, currentVersion); + DatabaseDocDiff currentDiff = retrieveDatabaseDocumentDiff(current); + DatabaseDocDiff originalDiff = retrieveDatabaseDocumentDiff(original); + return documentDiffChecker.diff(originalDiff.getTables(), currentDiff.getTables()); + } + + /** + * 检索数据库文档差异 + * @param databaseDocument + * @return + */ + private DatabaseDocDiff retrieveDatabaseDocumentDiff(MonitorDatabaseDocument databaseDocument) { + Integer docId = databaseDocument.getId(); + List tables = documentMapper.getTableDocument(docId,null); + List columns = tableColumnDocumentMapper.getTableColumn(docId); + List indexes = tableIndexDocumentMapper.getTableIndex(docId); + List triggers = tableForeignKeyDocumentMapper.getTableForeignKey(docId); + List fks = tableTriggerDocumentMapper.getTableTrigger(docId); + return documentDiffConverter.of(databaseDocument, tables, columns, indexes, fks, triggers); + } + + /** + * 获取表名列表 + * @param projectId + * @param version + * @param search + * @return + */ + @Override + public List> getTableDocumentByProjectId(Integer projectId, Long version, String search) { + MonitorDatabaseDocument monitorDatabaseDocument; + if (version == null) { + Long maxVersion = documentMapper.getMaxVersion(projectId); + monitorDatabaseDocument = documentMapper.getSimpleByProjectId(projectId,maxVersion); + } else { + monitorDatabaseDocument = documentMapper.getSimpleByProjectId(projectId,version); + } + List> list = new ArrayList<>(); + if (monitorDatabaseDocument != null) { + list = documentMapper.getTableDocumentById(monitorDatabaseDocument.getId(),search); + } + return list; + } + + /** + * 获取同步任务列表 + * @param taskIds + * @return + */ + @Override + public List getSyncTaskList(Integer[] taskIds) { + return documentMapper.getSyncTaskList(taskIds); + } + + /** + * 取消同步任务 + * @param taskId + * @return + */ + @Override + public int cancelTask(Integer taskId) { + return documentMapper.updateStatusAndResultById(taskId); + } + + /** + * 导出 + * @param projectId + * @param version + * @param fileType + * @param out + */ + @Override + public void export(Integer projectId, Long version, String fileType, OutputStream out) { + DocumentFileType type = null; + if ("Markdown".equals(fileType)) { + type = DocumentFileType.MARKDOWN; + } else if ("UML SVG".equals(fileType)) { + type = DocumentFileType.PLANT_UML_ER_SVG; + } else if ("UML PNG".equals(fileType)) { + type = DocumentFileType.PLANT_UML_ER_PNG; + } else if ("Word".equals(fileType)) { + type = DocumentFileType.WORD; + } + DatabaseDocumentResponse databaseDocumentResponse = null; + if (version == null) { + Long maxVersion = documentMapper.getMaxVersion(projectId); + databaseDocumentResponse = documentMapper.getByProjectId(projectId,maxVersion); + } else { + databaseDocumentResponse = documentMapper.getByProjectId(projectId,version); + } + List list = new ArrayList<>(); + if (databaseDocumentResponse != null) { + list = documentMapper.getTableDetails(databaseDocumentResponse.getId()); + if (list != null) { + for (TableDocumentResponse tableDocumentResponse : list) { + List list1 = tableColumnDocumentMapper.getTableColumnByTableId(tableDocumentResponse.getId()); + List list2 = tableIndexDocumentMapper.getTableIndexByTableId(tableDocumentResponse.getId()); + List list3 = tableForeignKeyDocumentMapper.getTableForeignKeyByTableId(tableDocumentResponse.getId()); + List list4 = tableTriggerDocumentMapper.getTableTriggerByTableId(tableDocumentResponse.getId()); + /*实体转换*/ + List columnList = list1.stream().map(a ->{ + //在此转把A转换为B + TableDocumentResponse.ColumnDocumentResponse columnDocumentResponse = new TableDocumentResponse.ColumnDocumentResponse(); + columnDocumentResponse.setId(a.getId()); + columnDocumentResponse.setName(a.getName()); + columnDocumentResponse.setType(a.getType()); + columnDocumentResponse.setSize(a.getSize()); + columnDocumentResponse.setDecimalDigits(a.getDecimalDigits()); + columnDocumentResponse.setComment(a.getComment()); + columnDocumentResponse.setIsPrimaryKey(a.getPrimaryKey()); + columnDocumentResponse.setNullable(a.getNullable()); + columnDocumentResponse.setAutoIncrement(a.getAutoIncrement()); + columnDocumentResponse.setDefaultValue(a.getDefaultValue()); + columnDocumentResponse.setCreateAt(a.getCreateAt()); + return columnDocumentResponse; + }).collect(Collectors.toList()); + tableDocumentResponse.setColumns(columnList); + + List indexList = list2.stream().map(a ->{ + //在此转把A转换为B + TableDocumentResponse.IndexDocumentResponse indexDocumentResponse = new TableDocumentResponse.IndexDocumentResponse(); + indexDocumentResponse.setId(a.getId()); + indexDocumentResponse.setName(a.getName()); + indexDocumentResponse.setIsUnique(a.getUnique()); + indexDocumentResponse.setCreateAt(a.getCreateAt()); + return indexDocumentResponse; + }).collect(Collectors.toList()); + tableDocumentResponse.setIndexes(indexList); + + List foreignList = list3.stream().map(a ->{ + //在此转把A转换为B + TableDocumentResponse.ForeignKeyDocumentResponse foreignKeyDocumentResponse = new TableDocumentResponse.ForeignKeyDocumentResponse(); + foreignKeyDocumentResponse.setId(a.getId()); + foreignKeyDocumentResponse.setFkName(a.getFkName()); + foreignKeyDocumentResponse.setFkTableName(a.getFkTableName()); + foreignKeyDocumentResponse.setFkColumnName(a.getFkColumnName()); + foreignKeyDocumentResponse.setKeySeq(a.getKeySeq()); + foreignKeyDocumentResponse.setPkName(a.getPkName()); + foreignKeyDocumentResponse.setPkTableName(a.getPkTableName()); + foreignKeyDocumentResponse.setPkColumnName(a.getPkColumnName()); + foreignKeyDocumentResponse.setUpdateRule(a.getUpdateRule()); + foreignKeyDocumentResponse.setDeleteRule(a.getDeleteRule()); + foreignKeyDocumentResponse.setCreateAt(a.getCreateAt()); + return foreignKeyDocumentResponse; + }).collect(Collectors.toList()); + tableDocumentResponse.setForeignKeys(foreignList); + + List triggerList = list4.stream().map(a ->{ + //在此转把A转换为B + TableDocumentResponse.TriggerDocumentResponse triggerDocumentResponse = new TableDocumentResponse.TriggerDocumentResponse(); + triggerDocumentResponse.setId(a.getId()); + triggerDocumentResponse.setName(a.getName()); + triggerDocumentResponse.setTiming(a.getTiming()); + triggerDocumentResponse.setManipulation(a.getManipulation()); + triggerDocumentResponse.setStatement(a.getStatement()); + triggerDocumentResponse.setTriggerCreateAt(a.getTriggerCreateAt()); + triggerDocumentResponse.setCreateAt(a.getCreateAt()); + return triggerDocumentResponse; + }).collect(Collectors.toList()); + tableDocumentResponse.setTriggers(triggerList); + } + } + } + databaseDocumentResponse.setTables(list); + var context = DocumentFileGenerator.DocumentFileGenerateContext.builder() + .documentFileType(type) + .databaseDocument(databaseDocumentResponse) + .build(); + DocumentFileType finalType = type; + documentFileGenerators.stream() + .filter(g -> g.support(finalType)) + .findFirst() + .ifPresent(generator -> generator.generate(context, out)); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentSyncServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentSyncServiceImpl.java new file mode 100644 index 0000000..255004c --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentSyncServiceImpl.java @@ -0,0 +1,300 @@ +package com.salpa.subject.service.impl; + +import com.salpa.subject.domain.*; +import com.salpa.subject.domain.data.*; +import com.salpa.subject.domain.diff.*; +import com.salpa.subject.domain.error.DomainErrors; +import com.salpa.subject.domain.error.MetaopsException; +import com.salpa.subject.mapper.DatabaseMetaConverter; +import com.salpa.subject.mapper.DocumentPojoConverter; +import com.salpa.subject.domain.converter.JsonConverter; +import com.salpa.subject.domain.diff.data.DiffType; +import com.salpa.subject.domain.diff.data.RootDiff; +import com.salpa.subject.mapper.*; +import com.salpa.subject.service.DocumentSyncService; +import com.salpa.subject.service.ProjectService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class DocumentSyncServiceImpl implements DocumentSyncService { + + @Autowired + private ProjectService projectService; + + @Autowired + private SubjectMapper subjectMapper; + + @Autowired + private DocumentMapper documentMapper; + + @Autowired + private ProjectSyncRuleMapper projectSyncRuleMapper; + + @Autowired + private DatabaseMapper databaseMapper; + + @Autowired + private DataSourcePropertyMapper dataSourcePropertyMapper; + + @Autowired + private TableColumnDocumentMapper tableColumnDocumentMapper; + + @Autowired + private TableIndexDocumentMapper tableIndexDocumentMapper; + + @Autowired + private TableForeignKeyDocumentMapper tableForeignKeyDocumentMapper; + + @Autowired + private TableTriggerDocumentMapper tableTriggerDocumentMapper; + + @Autowired + private JsonConverter jsonConverter; + + @Autowired + private ApplicationEventPublisher eventPublisher; + + @Autowired + private DocumentPojoConverter documentPojoConverter; + + @Autowired + DatabaseMetaConverter databaseMetaConverter; + + @Autowired + private WarningMapper warningMapper; + + /** + * 同步项目 + * @param projectId + */ + @Override + public Integer syncByProjectId(Integer projectId, Integer taskId) { + DatabaseMeta current = retrieveDatabaseMeta(projectId); + Optional originalOption = documentMapper.selectNotArchivedByProjectId(projectId); + Integer docId = null; + if (originalOption.isPresent()) { + if (!"CANCELED".equals(selectStatusById(taskId))) { + MonitorDatabaseDocument original = originalOption.get(); + DatabaseMeta originalMeta = retrieveOriginalDatabaseMeta(original); + RootDiff diff = Diffs.diff(originalMeta, current); + if (diff.getDiffType() == DiffType.NONE) { + log.info("ignore project {} {} sync data, because without change", + projectId, + original.getDatabaseName()); + return null; + } else { + Integer previousDocumentId = original.getId(); + // archive old version + documentMapper.updateIsArchiveById(previousDocumentId, true); + Long version = original.getDocumentVersion(); + docId = saveNewDocument(current, version + 1, original.getProjectId(), taskId); + /*添加告警信息*/ + if (version >= 1) { + MonitorWarning monitorWarning = new MonitorWarning(); + monitorWarning.setWarningName(original.getDatabaseName()+"数据库告警"); + monitorWarning.setWarningSubject(subjectMapper.getById(projectService.getById(projectId).getSubjectId()).getSubjectName() + "/" + projectService.getById(projectId).getProjectName()); + monitorWarning.setWarningDataSource(original.getDatabaseName()); + monitorWarning.setWarningTime(new Date()); + Long newVersion = version + 1; + monitorWarning.setWarningContent("数据库发生变化,最新文档版本为【" + newVersion + "】"); + warningMapper.addWarning(monitorWarning); + } + if (docId != null) { + eventPublisher.publishEvent(new DocumentUpdated(diff, version + 1, version, projectId, docId)); + } + } + } + } else { + if (!"CANCELED".equals(selectStatusById(taskId))) { + docId = saveNewDocument(current, 1L, projectId, taskId); + RootDiff diff = null; + try { + diff = Diffs.diff(null, current); + } catch (Exception e) { + log.error("diff project " + projectId + " error, fallback diff type to NONE", e); + } + if (docId != null) { + eventPublisher.publishEvent(new DocumentUpdated(diff, 1L, null, projectId, docId)); + } + } + } + return docId; + } + + /** + * 根据id查询任务状态 + * @param taskId + * @return + */ + @Override + public String selectStatusById(Integer taskId) { + return documentMapper.selectStatusById(taskId); + } + + /** + * 获取版本号 + * @return + */ + @Override + public Long getDocumentVersionById(Integer docId) { + return documentMapper.getDocumentVersionById(docId); + } + + /** + * 删除文档 + * @param docId + */ + @Override + public void deleteDocumentById(Integer docId) { + documentMapper.deleteDocumentById(docId); + } + + /** + * 恢复上个版本 + * @param projectId + * @param version + */ + @Override + public void updateDocument(Integer projectId, Long version) { + documentMapper.updateIsArchive(projectId,version); + } + + /** + * 连接数据源获取新数据 + * @param projectId + * @return + */ + private DatabaseMeta retrieveDatabaseMeta(Integer projectId) { + MonitorProjectSyncRule rule = projectSyncRuleMapper.selectByProjectId(projectId); + MonitorDataSource dataSource = databaseMapper.selectDataSourceById(projectId); + List> properties = dataSourcePropertyMapper.selectPropertyBySourceId(dataSource.getId()); + Connection jdbcConnection = projectService.create(dataSource, properties); + DatabasirConfig databasirConfig = new DatabasirConfig(); + databasirConfig.setIgnoreTableNameRegex(jsonConverter.fromJson(rule.getIgnoreTableNameRegexArray().substring(1,rule.getIgnoreTableNameRegexArray().length()-1))); + databasirConfig.setIgnoreTableColumnNameRegex(jsonConverter.fromJson(rule.getIgnoreColumnNameRegexArray().substring(1,rule.getIgnoreColumnNameRegexArray().length()-1))); + try { + if (jdbcConnection == null) { + throw DomainErrors.CONNECT_DATABASE_FAILED.exception(); + } + DatabaseMeta databaseMeta = Databasir.of(databasirConfig) + .get(jdbcConnection, dataSource.getDatabaseName(), dataSource.getSchemaName()) + .orElseThrow(DomainErrors.DATABASE_META_NOT_FOUND::exception); + return databaseMeta; + } finally { + try { + if (jdbcConnection != null && !jdbcConnection.isClosed()) { + jdbcConnection.close(); + } + } catch (SQLException e) { + log.error("close jdbc connection error", e); + } + } + } + + /** + * 恢复历史数据 + * @param original + * @return + */ + private DatabaseMeta retrieveOriginalDatabaseMeta(MonitorDatabaseDocument original) { + Integer docId = original.getId(); + List tables = documentMapper.getTableDocument(docId,null); + List columns = tableColumnDocumentMapper.getTableColumn(docId); + List indexes = tableIndexDocumentMapper.getTableIndex(docId); + List triggers = tableTriggerDocumentMapper.getTableTrigger(docId); + List fks = tableForeignKeyDocumentMapper.getTableForeignKey(docId); + return databaseMetaConverter.of(original, tables, columns, indexes, triggers, fks); + } + + /** + * 创建新文档 + * @param meta + * @param version + * @param projectId + * @return + */ + private Integer saveNewDocument(DatabaseMeta meta, + Long version, + Integer projectId, + Integer taskId) { + + MonitorDatabaseDocument dbDocPojo = new MonitorDatabaseDocument(); + dbDocPojo.setProjectId(projectId); + dbDocPojo.setDatabaseName(meta.getDatabaseName()); + dbDocPojo.setSchemaName(meta.getSchemaName()); + dbDocPojo.setProductName(meta.getProductName()); + dbDocPojo.setProductVersion(meta.getProductVersion()); + dbDocPojo.setDocumentVersion(version); + Integer docId = null; + try { + if (!"CANCELED".equals(selectStatusById(taskId))) { + documentMapper.addDatabaseDocument(dbDocPojo); + docId = dbDocPojo.getId(); + dbDocPojo.setId(docId); + } + } catch (DuplicateKeyException e) { + log.warn("ignore insert database document projectId={} version={}", projectId, version); + throw new MetaopsException(DomainErrors.DATABASE_DOCUMENT_DUPLICATE_KEY); + } + if (docId != null) { + Integer finalDocId = docId; + List list = meta.getTables(); + for (TableMeta table : list) { + MonitorTableDocument tableMeta = new MonitorTableDocument(); + tableMeta.setName(table.getName()); + tableMeta.setDatabaseDocumentId(finalDocId); + tableMeta.setType(table.getType()); + tableMeta.setComment(table.getComment()); + documentMapper.insertTableDocumentAndReturnId(tableMeta); + Integer tableMetaId = tableMeta.getId(); + tableMeta.setId(tableMetaId); + // column + var columns = documentPojoConverter.toColumnPojo(finalDocId, tableMetaId, table.getColumns()); + for (MonitorTableColumnDocument column : columns) { + tableColumnDocumentMapper.insertTableColumn(column); + } + // index + var indexes = documentPojoConverter.toIndexPojo(finalDocId, tableMetaId, table.getIndexes()) + .stream() + .filter(index -> { + if (index.getName() != null) { + return true; + } else { + return false; + } + }) + .collect(Collectors.toList()); + for (MonitorTableIndexDocument index : indexes) { + tableIndexDocumentMapper.insertTableIndex(index); + } + // foreign key + var foreignKeys = documentPojoConverter.toForeignKeyPojo(finalDocId, tableMetaId, table.getForeignKeys()); + for (MonitorTableForeignKeyDocument foreignKey : foreignKeys) { + tableForeignKeyDocumentMapper.insertTableForeignKey(foreignKey); + } + + // trigger + var triggers = documentPojoConverter.toTriggerPojo(finalDocId, tableMetaId, table.getTriggers()); + for (MonitorTableTriggerDocument trigger : triggers) { + tableTriggerDocumentMapper.insertTableTrigger(trigger); + } + } + } + log.info("save new version document success: projectId = {}, name = {}, version = {}", + projectId, meta.getDatabaseName(), version); + return docId; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentTemplateServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentTemplateServiceImpl.java new file mode 100644 index 0000000..8fb4f4a --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/DocumentTemplateServiceImpl.java @@ -0,0 +1,49 @@ +package com.salpa.subject.service.impl; + +import com.salpa.subject.domain.MonitorDocumentTemplateProperty; +import com.salpa.subject.domain.data.DocumentTemplatePropertiesResponse; +import com.salpa.subject.mapper.DocumentTemplatePropertyMapper; +import com.salpa.subject.service.DocumentTemplateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.salpa.subject.domain.data.DocumentTemplatePropertyType.*; + +@Service +public class DocumentTemplateServiceImpl implements DocumentTemplateService { + + @Autowired + private DocumentTemplatePropertyMapper documentTemplatePropertyMapper; + + /** + * 获取模板属性 + * @return + */ + @Override + public DocumentTemplatePropertiesResponse getAllProperties() { + List properties = documentTemplatePropertyMapper.selectAll(); + List list = properties.stream().map(a ->{ + //在此转把A转换为B + DocumentTemplatePropertiesResponse.DocumentTemplatePropertyResponse documentTemplatePropertyResponse = new DocumentTemplatePropertiesResponse.DocumentTemplatePropertyResponse(); + documentTemplatePropertyResponse.setKey(a.getKey()); + documentTemplatePropertyResponse.setValue(a.getValue()); + documentTemplatePropertyResponse.setDefaultValue(a.getDefaultValue()); + documentTemplatePropertyResponse.setType(a.getType()); + documentTemplatePropertyResponse.setCreateAt(a.getCreateAt()); + return documentTemplatePropertyResponse; + }).collect(Collectors.toList()); + var propertiesGroupByType = list + .stream() + .collect(Collectors.groupingBy(d -> d.getType())); + return DocumentTemplatePropertiesResponse.builder() + .tableFieldNameProperties(propertiesGroupByType.get(TABLE_FIELD_NAME)) + .columnFieldNameProperties(propertiesGroupByType.get(COLUMN_FIELD_NAME)) + .foreignKeyFieldNameProperties(propertiesGroupByType.get(FOREIGN_KEY_FIELD_NAME)) + .indexFieldNameProperties(propertiesGroupByType.get(INDEX_FIELD_NAME)) + .triggerFieldNameProperties(propertiesGroupByType.get(TRIGGER_FIELD_NAME)) + .build(); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/ProjectServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/ProjectServiceImpl.java new file mode 100644 index 0000000..aca65f8 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/ProjectServiceImpl.java @@ -0,0 +1,391 @@ +package com.salpa.subject.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.github.pagehelper.PageHelper; +import com.salpa.common.constant.UserConstants; +import com.salpa.subject.domain.*; +import com.salpa.subject.domain.vo.MonitorDataSourceVo; +import com.salpa.subject.domain.vo.OperationLogVo; +import com.salpa.subject.domain.vo.ProjectSyncRule; +import com.salpa.subject.mapper.*; +import com.salpa.subject.service.ProjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * 项目管理实现类 + * + * @author zhuff + */ +@Service +public class ProjectServiceImpl implements ProjectService { + + @Autowired + private ProjectMapper projectMapper; + + @Autowired + private DatabaseMapper databaseMapper; + + @Autowired + private DataSourcePropertyMapper dataSourcePropertyMapper; + + @Autowired + private ProjectSyncRuleMapper projectSyncRuleMapper; + + @Autowired + private DatabaseTypeMapper databaseTypeMapper; + + @Autowired + private OperationLogMapper operationLogMapper; + + /** + * 获取数据源管理列表 + * @author zhuff + * @param monitorDataSourceVo + * @return + */ + @Override + public List> getList(MonitorDataSourceVo monitorDataSourceVo) { + PageHelper.startPage(monitorDataSourceVo.getPageNum(),monitorDataSourceVo.getPageSize()); + List> list = projectMapper.getList(monitorDataSourceVo); + for (Map map : list) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String createAt1 = dateFormat.format(map.get("createAt")); + map.put("createAt",createAt1); + if (map.get("isAutoSync").equals(false)) { + map.put("isAutoSync",0); + } else { + map.put("isAutoSync",1); + } + } + return list; + } + + /** + * 验证项目是否已经存在 + * @param projectName + * @return + */ + @Override + public String checkProjectNameUnique(String projectName) { + int count = projectMapper.checkProjectNameUnique(projectName); + if (count > 0) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 新增项目及数据库相关信息 + * @param monitorProject + * @return + */ + @Override + public int addProject(MonitorProject monitorProject) { + /*新增项目*/ + int resultInt = projectMapper.addProject(monitorProject); + /*新增数据库*/ + monitorProject.getMonitorDataSource().setProjectId(monitorProject.getId()); + databaseMapper.addDatabaseSource(monitorProject.getMonitorDataSource()); + /*新增数据库参数*/ + Map[] properties = monitorProject.getMonitorDataSource().getProperties(); + if (properties != null){ + for (int i = 0; i < properties.length; i++) { + MonitorDataSourceProperty monitorDataSourceProperty = new MonitorDataSourceProperty(); + monitorDataSourceProperty.setDataSourceId(monitorProject.getMonitorDataSource().getId()); + monitorDataSourceProperty.setDataSourceKey(properties[i].get("dataSourceKey").toString()); + monitorDataSourceProperty.setDataSourceValue(properties[i].get("dataSourceValue").toString()); + dataSourcePropertyMapper.addDataSourceProperty(monitorDataSourceProperty); + } + } + /*新增项目高级配置*/ + monitorProject.getProjectSyncRule().setProjectId(monitorProject.getId()); + String ignoreTableNameRegex = ""; + if (monitorProject.getProjectSyncRule().getIgnoreTableNameRegexArray()!=null){ + String[] string = monitorProject.getProjectSyncRule().getIgnoreTableNameRegexArray(); + for (int i = 0; i < string.length; i++) { + if (ignoreTableNameRegex == "") { + ignoreTableNameRegex += string[i]; + } else { + ignoreTableNameRegex += "," + string[i]; + } + } + } + String ignoreColumnNameRegex = ""; + if (monitorProject.getProjectSyncRule().getIgnoreColumnNameRegexArray()!=null){ + String[] string1 = monitorProject.getProjectSyncRule().getIgnoreColumnNameRegexArray(); + for (int i = 0; i < string1.length; i++) { + if (ignoreColumnNameRegex == "") { + ignoreColumnNameRegex += string1[i]; + } else { + ignoreColumnNameRegex += "," + string1[i]; + } + } + } + projectSyncRuleMapper.addProjectSyncRule(monitorProject.getProjectSyncRule(),JSONObject.toJSONString(ignoreTableNameRegex),JSONObject.toJSONString(ignoreColumnNameRegex)); + return resultInt; + } + + /** + * 根据id获取项目 + * @param id + * @return + */ + @Override + public MonitorProject getProjectById(Integer id) { + MonitorProject monitorProject = projectMapper.selectById(id); + MonitorDataSource monitorDataSource = databaseMapper.selectDataSourceById(id); + List> list = dataSourcePropertyMapper.selectPropertyBySourceId(monitorDataSource.getId()); + if (list != null){ + Map[] properties = new Map[list.size()]; + int i = 0; + for (Map map : list) { + properties[i] = map; + i++; + } + monitorDataSource.setProperties(properties); + } + monitorProject.setMonitorDataSource(monitorDataSource); + MonitorProjectSyncRule monitorProjectSyncRule = projectSyncRuleMapper.selectByProjectId(id); + if (monitorProjectSyncRule != null) { + ProjectSyncRule projectSyncRule = new ProjectSyncRule(); + String[] split = monitorProjectSyncRule.getIgnoreTableNameRegexArray().substring(1,monitorProjectSyncRule.getIgnoreTableNameRegexArray().length()-1).split(","); + String[] split1 = monitorProjectSyncRule.getIgnoreColumnNameRegexArray().substring(1,monitorProjectSyncRule.getIgnoreColumnNameRegexArray().length()-1).split(","); + if (split.length>0 && !split[0].equals("")){ + projectSyncRule.setIgnoreTableNameRegexArray(split); + } + if (split1.length>0 && !split1[0].equals("")){ + projectSyncRule.setIgnoreColumnNameRegexArray(split1); + } + projectSyncRule.setAutoSync(monitorProjectSyncRule.isAutoSync()); + projectSyncRule.setAutoSyncCron(monitorProjectSyncRule.getAutoSyncCron()); + monitorProject.setProjectSyncRule(projectSyncRule); + } + return monitorProject; + } + + /** + * 验证数据库是否已经存在 + * @param databaseName + * @return + */ + @Override + public String checkDatabaseNameUnique(String databaseName) { + int count = projectMapper.checkDatabaseNameUnique(databaseName); + if (count > 0) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 获取项目 + * @param id + * @return + */ + @Override + public MonitorProject getById(Integer id) { + return projectMapper.selectById(id); + } + + /** + * 获取数据库 + * @param id + * @return + */ + @Override + public MonitorDataSource getDataSource(Integer id) { + return databaseMapper.selectDataSourceById(id); + } + + /** + * 修改项目及数据库相关信息 + * @param monitorProject + * @return + */ + @Override + public int updateProject(MonitorProject monitorProject) { + /*修改数据库*/ + databaseMapper.updateDateSource(monitorProject.getMonitorDataSource()); + /*修改数据库参数*/ + dataSourcePropertyMapper.deleteDataSourceProperty(monitorProject.getMonitorDataSource().getId()); + Map[] properties = monitorProject.getMonitorDataSource().getProperties(); + if (properties != null){ + for (int i = 0; i < properties.length; i++) { + MonitorDataSourceProperty monitorDataSourceProperty = new MonitorDataSourceProperty(); + monitorDataSourceProperty.setDataSourceId(monitorProject.getMonitorDataSource().getId()); + monitorDataSourceProperty.setDataSourceKey(properties[i].get("dataSourceKey").toString()); + monitorDataSourceProperty.setDataSourceValue(properties[i].get("dataSourceValue").toString()); + dataSourcePropertyMapper.addDataSourceProperty(monitorDataSourceProperty); + } + } + /*修改项目高级配置*/ + projectSyncRuleMapper.deletProjectSyncRule(monitorProject.getId()); + monitorProject.getProjectSyncRule().setProjectId(monitorProject.getId()); + String ignoreTableNameRegex = ""; + if (monitorProject.getProjectSyncRule().getIgnoreTableNameRegexArray() !=null){ + String[] string = monitorProject.getProjectSyncRule().getIgnoreTableNameRegexArray(); + for (int i = 0; i < string.length; i++) { + if (ignoreTableNameRegex == "") { + ignoreTableNameRegex += string[i]; + } else { + ignoreTableNameRegex += "," + string[i]; + } + } + } + String ignoreColumnNameRegex = ""; + if (monitorProject.getProjectSyncRule().getIgnoreColumnNameRegexArray() != null) { + String[] string1 = monitorProject.getProjectSyncRule().getIgnoreColumnNameRegexArray(); + for (int i = 0; i < string1.length; i++) { + if (ignoreColumnNameRegex == "") { + ignoreColumnNameRegex += string1[i]; + } else { + ignoreColumnNameRegex += "," + string1[i]; + } + } + } + projectSyncRuleMapper.addProjectSyncRule(monitorProject.getProjectSyncRule(),JSONObject.toJSONString(ignoreTableNameRegex),JSONObject.toJSONString(ignoreColumnNameRegex)); + /*修改项目*/ + return projectMapper.updateProject(monitorProject); + } + + /** + * 删除项目 + * @param id + * @return + */ + @Override + public int deleteProject(Integer id) { + /*删除项目高级配置*/ + projectSyncRuleMapper.deletProjectSyncRule(id); + /*删除数据库参数*/ + dataSourcePropertyMapper.deleteDataSourceProperty(databaseMapper.selectDataSourceById(id).getId()); + /*删除数据库*/ + databaseMapper.deleteDataSource(id); + /*删除项目*/ + return projectMapper.deleteProject(id); + } + + /** + * 测试连接 + * @param monitorDataSource + */ + @Override + public int testConnection(MonitorDataSource monitorDataSource) { + MonitorDatabaseType monitorDatabaseType = databaseTypeMapper.getTypeByName(monitorDataSource.getDatabaseType()); + /*这是要连接的数据库加载器*/ + String driverName = monitorDatabaseType.getJdbcDriverClassName(); + /*这是要连接的端口号以及数据库名称*/ + String urlPattern = monitorDatabaseType.getUrlPattern(); + String dbURL = urlPattern.replace("{{jdbc.protocol}}", monitorDatabaseType.getJdbcProtocol()) + .replace("{{db.url}}", monitorDataSource.getUrl()) + .replace("{{db.name}}", monitorDataSource.getDatabaseName()) + .replace("{{db.schema}}", monitorDataSource.getSchemaName()); + Properties info = new Properties(); + /*连接参数*/ + if (monitorDataSource.getProperties() != null) { + for (int i = 0; i < monitorDataSource.getProperties().length; i++) { + info.put(monitorDataSource.getProperties()[i].get("dataSourceKey"),monitorDataSource.getProperties()[i].get("dataSourceValue")); + } + } + /*用户名*/ + String userName = monitorDataSource.getUsername(); + /*用户密码*/ + String userpwd = monitorDataSource.getPassword(); + info.put("user" ,userName); + info.put("password" ,userpwd); + int i = 0; + try { + Class.forName(driverName); + }catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + i = 1; + } + try { + DriverManager.getConnection(dbURL,info); + i = 3; + }catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + i = 2; + } + return i; + } + + /** + * 获取日志列表 + * @param operationLogVo + * @return + */ + @Override + public List getLogList(OperationLogVo operationLogVo) { + PageHelper.startPage(operationLogVo.getPageNum(),operationLogVo.getPageSize()); + List list = operationLogMapper.getList(operationLogVo.getProjectId()); + for (MonitorOperationLog monitorOperationLog : list) { + Map map = (Map) JSON.parse(monitorOperationLog.getOperationResponse()); + monitorOperationLog.setOperationResponse(map.get("msg").toString()); + } + return list; + } + + /** + * 连接数据源 + * @param dataSource + * @param properties + * @return + */ + @Override + public Connection create(MonitorDataSource dataSource, List> properties) { + MonitorDatabaseType monitorDatabaseType = databaseTypeMapper.getTypeByName(dataSource.getDatabaseType()); + /*这是要连接的数据库加载器*/ + String driverName = monitorDatabaseType.getJdbcDriverClassName(); + /*这是要连接的端口号以及数据库名称*/ + String urlPattern = monitorDatabaseType.getUrlPattern(); + String dbURL = urlPattern.replace("{{jdbc.protocol}}", monitorDatabaseType.getJdbcProtocol()) + .replace("{{db.url}}", dataSource.getUrl()) + .replace("{{db.name}}", dataSource.getDatabaseName()) + .replace("{{db.schema}}", dataSource.getSchemaName()); + Properties info = new Properties(); + /*连接参数*/ + if (properties != null) { + for (Map property : properties) { + info.put(property.get("dataSourceKey"),property.get("dataSourceValue")); + } + } + /*用户名*/ + String userName = dataSource.getUsername(); + /*用户密码*/ + String userpwd = dataSource.getPassword(); + info.put("user" ,userName); + info.put("password" ,userpwd); + try { + Class.forName(driverName); + System.out.println("驱动加载成功"); + }catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + System.out.println("驱动加载失败"); + } + Connection connection = null; + try { + connection = DriverManager.getConnection(dbURL, info); + System.out.println("数据库连接成功"); + }catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + System.out.println("数据库连接失败"); + } + return connection; + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/SubjectServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/SubjectServiceImpl.java new file mode 100644 index 0000000..8ec607f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/SubjectServiceImpl.java @@ -0,0 +1,96 @@ +package com.salpa.subject.service.impl; + +import com.github.pagehelper.PageHelper; +import com.salpa.common.constant.UserConstants; +import com.salpa.subject.domain.MonitorSubject; +import com.salpa.subject.mapper.SubjectMapper; +import com.salpa.subject.service.SubjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class SubjectServiceImpl implements SubjectService { + + @Autowired + private SubjectMapper subjectMapper; + + /** + * 获取主题管理列表 + * @author zhuff + * @param pageNum + * @param pageSize + * @param subjectName + * @return + */ + @Override + public List getList(Integer pageNum,Integer pageSize,String subjectName) { + PageHelper.startPage(pageNum,pageSize); + List list = subjectMapper.getList(subjectName); + for (MonitorSubject monitorSubject : list) { + int count = subjectMapper.selectCount(monitorSubject.getId()); + monitorSubject.setProjectCount(count); + } + return list; + } + + /** + * 根据项目id获取主题管理 + * @author zhuff + * @param id + * @return + */ + @Override + public MonitorSubject getById(Integer id) { + return subjectMapper.getById(id); + } + + /** + * 判断主题名称是否存在 + * @param subjectName + * @return + */ + @Override + public String checkSubjectNameUnique(String subjectName) { + int count = subjectMapper.checkSubjectNameUnique(subjectName); + if (count > 0) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 新增主题管理 + * @author zhuff + * @param monitorSubject + * @return + */ + @Override + public int addSubject(MonitorSubject monitorSubject) { + return subjectMapper.addSubject(monitorSubject); + } + + /** + * 修改主题管理 + * @author zhuff + * @param monitorSubject + * @return + */ + @Override + public int updateSubject(MonitorSubject monitorSubject) { + return subjectMapper.updateSubject(monitorSubject); + } + + /** + * 删除主题管理 + * @author zhuff + * @param id + * @return + */ + @Override + public int deleteSubject(Integer id) { + return subjectMapper.deleteSubject(id); + } +} diff --git a/salpa-system/src/main/java/com/salpa/subject/service/impl/WarningServiceImpl.java b/salpa-system/src/main/java/com/salpa/subject/service/impl/WarningServiceImpl.java new file mode 100644 index 0000000..08acdcb --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/subject/service/impl/WarningServiceImpl.java @@ -0,0 +1,48 @@ +package com.salpa.subject.service.impl; + +import com.github.pagehelper.PageHelper; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.subject.domain.MonitorWarning; +import com.salpa.subject.mapper.WarningMapper; +import com.salpa.subject.service.WarningService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 告警管理业务层 + * @author zff + */ +@Service +public class WarningServiceImpl implements WarningService { + + @Autowired + private WarningMapper warningMapper; + + /** + * 获取告警列表 + * @param pageNum + * @param pageSize + * @return + */ + @Override + public List warningList(Integer pageNum, Integer pageSize) { + PageHelper.startPage(pageNum,pageSize); + List list = warningMapper.warningList(); + return list; + } + + /** + * 修改告警状态 + * @param id + * @return + */ + @Override + public int updateStatus(Long id) { + String userName = SecurityUtils.getLoginUser().getUser().getNickName(); + return warningMapper.updateStatus(id,userName); + } +} diff --git a/salpa-system/src/main/java/com/salpa/system/domain/SysCache.java b/salpa-system/src/main/java/com/salpa/system/domain/SysCache.java new file mode 100644 index 0000000..5d73ea9 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysCache.java @@ -0,0 +1,81 @@ +package com.salpa.system.domain; + +import com.salpa.common.utils.StringUtils; + +/** + * 缓存信息 + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/domain/SysConfig.java b/salpa-system/src/main/java/com/salpa/system/domain/SysConfig.java new file mode 100644 index 0000000..8c6d854 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysConfig.java @@ -0,0 +1,111 @@ +package com.salpa.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 参数配置表 sys_config + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/domain/SysLogininfor.java b/salpa-system/src/main/java/com/salpa/system/domain/SysLogininfor.java new file mode 100644 index 0000000..702ca09 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysLogininfor.java @@ -0,0 +1,144 @@ +package com.salpa.system.domain; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 系统访问记录表 sys_logininfor + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/domain/SysNotice.java b/salpa-system/src/main/java/com/salpa/system/domain/SysNotice.java new file mode 100644 index 0000000..45241f9 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysNotice.java @@ -0,0 +1,102 @@ +package com.salpa.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.core.domain.BaseEntity; +import com.salpa.common.xss.Xss; + +/** + * 通知公告表 sys_notice + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/domain/SysOperLog.java b/salpa-system/src/main/java/com/salpa/system/domain/SysOperLog.java new file mode 100644 index 0000000..08364a3 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysOperLog.java @@ -0,0 +1,255 @@ +package com.salpa.system.domain; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 操作日志记录表 oper_log + * + * @author salpa + */ +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; + + 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; + } +} diff --git a/salpa-system/src/main/java/com/salpa/system/domain/SysPost.java b/salpa-system/src/main/java/com/salpa/system/domain/SysPost.java new file mode 100644 index 0000000..87b654c --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysPost.java @@ -0,0 +1,123 @@ +package com.salpa.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.salpa.common.annotation.Excel; +import com.salpa.common.annotation.Excel.ColumnType; +import com.salpa.common.core.domain.BaseEntity; + +/** + * 岗位表 sys_post + * + * @author salpa + */ +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 String 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; + } + + @NotBlank(message = "显示顺序不能为空") + public String getPostSort() + { + return postSort; + } + + public void setPostSort(String 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/salpa-system/src/main/java/com/salpa/system/domain/SysRoleDept.java b/salpa-system/src/main/java/com/salpa/system/domain/SysRoleDept.java new file mode 100644 index 0000000..e378c65 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysRoleDept.java @@ -0,0 +1,46 @@ +package com.salpa.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 角色和部门关联 sys_role_dept + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/domain/SysRoleMenu.java b/salpa-system/src/main/java/com/salpa/system/domain/SysRoleMenu.java new file mode 100644 index 0000000..0d1ed4b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysRoleMenu.java @@ -0,0 +1,46 @@ +package com.salpa.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 角色和菜单关联 sys_role_menu + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/domain/SysUserOnline.java b/salpa-system/src/main/java/com/salpa/system/domain/SysUserOnline.java new file mode 100644 index 0000000..947769b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysUserOnline.java @@ -0,0 +1,113 @@ +package com.salpa.system.domain; + +/** + * 当前在线会话 + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/domain/SysUserPost.java b/salpa-system/src/main/java/com/salpa/system/domain/SysUserPost.java new file mode 100644 index 0000000..98e4391 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysUserPost.java @@ -0,0 +1,46 @@ +package com.salpa.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 用户和岗位关联 sys_user_post + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/domain/SysUserRole.java b/salpa-system/src/main/java/com/salpa/system/domain/SysUserRole.java new file mode 100644 index 0000000..74ac831 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/SysUserRole.java @@ -0,0 +1,58 @@ +package com.salpa.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 用户和角色关联 sys_user_role + * + * @author salpa + */ +public class SysUserRole +{ + /** 用户ID */ + private Long userId; + + /** 角色ID */ + private Long roleId; + + /** 项目ID */ + private String projectIds; + + 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; + } + + public String getProjectIds() { + return projectIds; + } + + public void setProjectIds(String projectIds) { + this.projectIds = projectIds; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("roleId", getRoleId()) + .append("projectIds", getProjectIds()) + .toString(); + } +} diff --git a/salpa-system/src/main/java/com/salpa/system/domain/vo/MetaVo.java b/salpa-system/src/main/java/com/salpa/system/domain/vo/MetaVo.java new file mode 100644 index 0000000..5903be2 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/vo/MetaVo.java @@ -0,0 +1,106 @@ +package com.salpa.system.domain.vo; + +import com.salpa.common.utils.StringUtils; + +/** + * 路由显示信息 + * + * @author salpa + */ +public class MetaVo +{ + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + 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/salpa-system/src/main/java/com/salpa/system/domain/vo/RouterVo.java b/salpa-system/src/main/java/com/salpa/system/domain/vo/RouterVo.java new file mode 100644 index 0000000..a395f33 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/domain/vo/RouterVo.java @@ -0,0 +1,161 @@ +package com.salpa.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; + +/** + * 路由配置信息 + * + * @author salpa + */ +@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 String perms; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List 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 String getPerms() { + return perms; + } + + public void setPerms(String perms) { + this.perms = perms; + } + + public MetaVo getMeta() + { + return meta; + } + + public void setMeta(MetaVo meta) + { + this.meta = meta; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/salpa-system/src/main/java/com/salpa/system/mapper/SysConfigMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysConfigMapper.java new file mode 100644 index 0000000..21ea56d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysConfigMapper.java @@ -0,0 +1,68 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.system.domain.SysConfig; + +/** + * 参数配置 数据层 + * + * @author salpa + */ +public interface SysConfigMapper +{ + /** + * 查询参数配置信息 + * + * @param config 参数配置信息 + * @return 参数配置信息 + */ + public SysConfig selectConfig(SysConfig config); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/mapper/SysDeptMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysDeptMapper.java new file mode 100644 index 0000000..f6fc2a2 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysDeptMapper.java @@ -0,0 +1,118 @@ +package com.salpa.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.salpa.common.core.domain.entity.SysDept; + +/** + * 部门管理 数据层 + * + * @author salpa + */ +public interface SysDeptMapper +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + public List 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 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 depts); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/salpa-system/src/main/java/com/salpa/system/mapper/SysDictDataMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysDictDataMapper.java new file mode 100644 index 0000000..0bd1447 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysDictDataMapper.java @@ -0,0 +1,95 @@ +package com.salpa.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.salpa.common.core.domain.entity.SysDictData; + +/** + * 字典表 数据层 + * + * @author salpa + */ +public interface SysDictDataMapper +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/mapper/SysDictTypeMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..5cd4da3 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,83 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.common.core.domain.entity.SysDictType; + +/** + * 字典表 数据层 + * + * @author salpa + */ +public interface SysDictTypeMapper +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/mapper/SysLogininforMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysLogininforMapper.java new file mode 100644 index 0000000..5d479df --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysLogininforMapper.java @@ -0,0 +1,42 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 数据层 + * + * @author salpa + */ +public interface SysLogininforMapper +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + public int cleanLogininfor(); +} diff --git a/salpa-system/src/main/java/com/salpa/system/mapper/SysMenuMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..6b13997 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysMenuMapper.java @@ -0,0 +1,117 @@ +package com.salpa.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.salpa.common.core.domain.entity.SysMenu; + +/** + * 菜单表 数据层 + * + * @author salpa + */ +public interface SysMenuMapper +{ + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu); + + /** + * 根据用户所有权限 + * + * @return 权限列表 + */ + public List selectMenuPerms(); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuListByUserId(SysMenu menu); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List selectMenuPermsByUserId(Long userId); + + /** + * 根据用户ID查询菜单 + * + * @return 菜单列表 + */ + public List selectMenuTreeAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @return 选中菜单列表 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/mapper/SysNoticeMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysNoticeMapper.java new file mode 100644 index 0000000..0119ed2 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysNoticeMapper.java @@ -0,0 +1,60 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.system.domain.SysNotice; + +/** + * 通知公告表 数据层 + * + * @author salpa + */ +public interface SysNoticeMapper +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/mapper/SysOperLogMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysOperLogMapper.java new file mode 100644 index 0000000..7063d8d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysOperLogMapper.java @@ -0,0 +1,48 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.system.domain.SysOperLog; + +/** + * 操作日志 数据层 + * + * @author salpa + */ +public interface SysOperLogMapper +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/mapper/SysPostMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysPostMapper.java new file mode 100644 index 0000000..cf59067 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysPostMapper.java @@ -0,0 +1,99 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.system.domain.SysPost; + +/** + * 岗位信息 数据层 + * + * @author salpa + */ +public interface SysPostMapper +{ + /** + * 查询岗位数据集合 + * + * @param post 岗位信息 + * @return 岗位数据集合 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleDeptMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 0000000..22932cf --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,44 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.system.domain.SysRoleDept; + +/** + * 角色与部门关联表 数据层 + * + * @author salpa + */ +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 roleDeptList); +} diff --git a/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..d24c23f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleMapper.java @@ -0,0 +1,107 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.common.core.domain.entity.SysRole; + +/** + * 角色表 数据层 + * + * @author salpa + */ +public interface SysRoleMapper +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 根据用户ID查询角色 + * + * @param userName 用户名 + * @return 角色列表 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleMenuMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..c8f9978 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,44 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.system.domain.SysRoleMenu; + +/** + * 角色与菜单关联表 数据层 + * + * @author salpa + */ +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 roleMenuList); +} diff --git a/salpa-system/src/main/java/com/salpa/system/mapper/SysUserMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..fe82ac1 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysUserMapper.java @@ -0,0 +1,127 @@ +package com.salpa.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.salpa.common.core.domain.entity.SysUser; + +/** + * 用户表 数据层 + * + * @author salpa + */ +public interface SysUserMapper +{ + /** + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser sysUser); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List 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 int checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); +} diff --git a/salpa-system/src/main/java/com/salpa/system/mapper/SysUserPostMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysUserPostMapper.java new file mode 100644 index 0000000..07a802a --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysUserPostMapper.java @@ -0,0 +1,44 @@ +package com.salpa.system.mapper; + +import java.util.List; +import com.salpa.system.domain.SysUserPost; + +/** + * 用户与岗位关联表 数据层 + * + * @author salpa + */ +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 userPostList); +} diff --git a/salpa-system/src/main/java/com/salpa/system/mapper/SysUserRoleMapper.java b/salpa-system/src/main/java/com/salpa/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..b14ac24 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,68 @@ +package com.salpa.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.salpa.system.domain.SysUserRole; + +/** + * 用户与角色关联表 数据层 + * + * @author salpa + */ +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 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); + + /** + * 获取列表 + * @return + */ + List getUserRole(); +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/ISysConfigService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysConfigService.java new file mode 100644 index 0000000..4e3a916 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysConfigService.java @@ -0,0 +1,89 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.system.domain.SysConfig; + +/** + * 参数配置 服务层 + * + * @author salpa + */ +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 selectCaptchaOnOff(); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List 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 String checkConfigKeyUnique(SysConfig config); +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/ISysDeptService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysDeptService.java new file mode 100644 index 0000000..7ee01de --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysDeptService.java @@ -0,0 +1,116 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.common.core.domain.TreeSelect; +import com.salpa.common.core.domain.entity.SysDept; + +/** + * 部门管理 服务层 + * + * @author salpa + */ +public interface ISysDeptService +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + public List buildDeptTree(List depts); + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + public List buildDeptTreeSelect(List depts); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + public List 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 String 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/salpa-system/src/main/java/com/salpa/system/service/ISysDictDataService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysDictDataService.java new file mode 100644 index 0000000..3bea9f7 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysDictDataService.java @@ -0,0 +1,60 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.common.core.domain.entity.SysDictData; + +/** + * 字典 业务层 + * + * @author salpa + */ +public interface ISysDictDataService +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/service/ISysDictTypeService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysDictTypeService.java new file mode 100644 index 0000000..8a2435e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysDictTypeService.java @@ -0,0 +1,98 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.common.core.domain.entity.SysDictData; +import com.salpa.common.core.domain.entity.SysDictType; + +/** + * 字典 业务层 + * + * @author salpa + */ +public interface ISysDictTypeService +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List 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 String checkDictTypeUnique(SysDictType dictType); +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/ISysLogininforService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysLogininforService.java new file mode 100644 index 0000000..8581d4b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysLogininforService.java @@ -0,0 +1,40 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 服务层 + * + * @author salpa + */ +public interface ISysLogininforService +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + */ + public void cleanLogininfor(); +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/ISysMenuService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysMenuService.java new file mode 100644 index 0000000..4528e97 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysMenuService.java @@ -0,0 +1,136 @@ +package com.salpa.system.service; + +import java.util.List; +import java.util.Set; +import com.salpa.common.core.domain.TreeSelect; +import com.salpa.common.core.domain.entity.SysMenu; +import com.salpa.system.domain.vo.RouterVo; + +/** + * 菜单 业务层 + * + * @author salpa + */ +public interface ISysMenuService +{ + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(Long userId); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu, Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectMenuPermsByUserId(Long userId); + + /** + * 根据用户ID查询菜单树信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(Long roleId); + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + public List buildMenus(List menus); + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + public List buildMenuTree(List menus); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + public List buildMenuTreeSelect(List 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 String checkMenuNameUnique(SysMenu menu); +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/ISysNoticeService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysNoticeService.java new file mode 100644 index 0000000..b7e1761 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysNoticeService.java @@ -0,0 +1,60 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.system.domain.SysNotice; + +/** + * 公告 服务层 + * + * @author salpa + */ +public interface ISysNoticeService +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/service/ISysOperLogService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysOperLogService.java new file mode 100644 index 0000000..9aac073 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysOperLogService.java @@ -0,0 +1,48 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.system.domain.SysOperLog; + +/** + * 操作日志 服务层 + * + * @author salpa + */ +public interface ISysOperLogService +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List 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/salpa-system/src/main/java/com/salpa/system/service/ISysPostService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysPostService.java new file mode 100644 index 0000000..8fb469d --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysPostService.java @@ -0,0 +1,99 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.system.domain.SysPost; + +/** + * 岗位信息 服务层 + * + * @author salpa + */ +public interface ISysPostService +{ + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位列表 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 校验岗位名称 + * + * @param post 岗位信息 + * @return 结果 + */ + public String checkPostNameUnique(SysPost post); + + /** + * 校验岗位编码 + * + * @param post 岗位信息 + * @return 结果 + */ + public String 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/salpa-system/src/main/java/com/salpa/system/service/ISysRoleService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysRoleService.java new file mode 100644 index 0000000..20d4c3e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysRoleService.java @@ -0,0 +1,173 @@ +package com.salpa.system.service; + +import java.util.List; +import java.util.Set; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.system.domain.SysUserRole; + +/** + * 角色业务层 + * + * @author salpa + */ +public interface ISysRoleService +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolesByUserId(Long userId); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public String checkRoleNameUnique(SysRole role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public String checkRoleKeyUnique(SysRole role); + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + public void checkRoleAllowed(SysRole role); + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + public void checkRoleDataScope(Long roleId); + + /** + * 通过角色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/salpa-system/src/main/java/com/salpa/system/service/ISysUserOnlineService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysUserOnlineService.java new file mode 100644 index 0000000..1aab0f9 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysUserOnlineService.java @@ -0,0 +1,48 @@ +package com.salpa.system.service; + +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.system.domain.SysUserOnline; + +/** + * 在线用户 服务层 + * + * @author salpa + */ +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/salpa-system/src/main/java/com/salpa/system/service/ISysUserService.java b/salpa-system/src/main/java/com/salpa/system/service/ISysUserService.java new file mode 100644 index 0000000..16458e5 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/ISysUserService.java @@ -0,0 +1,206 @@ +package com.salpa.system.service; + +import java.util.List; +import com.salpa.common.core.domain.entity.SysUser; + +/** + * 用户 业务层 + * + * @author salpa + */ +public interface ISysUserService +{ + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser user); + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List 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 userName 用户名称 + * @return 结果 + */ + public String checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public String checkPhoneUnique(SysUser user); + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public String 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 userList, Boolean isUpdateSupport, String operName); +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/impl/SysConfigServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..6fe9e25 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,226 @@ +package com.salpa.system.service.impl; + +import java.util.Collection; +import java.util.List; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.salpa.common.annotation.DataSource; +import com.salpa.common.constant.CacheConstants; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.redis.RedisCache; +import com.salpa.common.core.text.Convert; +import com.salpa.common.enums.DataSourceType; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.domain.SysConfig; +import com.salpa.system.mapper.SysConfigMapper; +import com.salpa.system.service.ISysConfigService; + +/** + * 参数配置 服务层实现 + * + * @author salpa + */ +@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 selectCaptchaOnOff() + { + String captchaOnOff = selectConfigByKey("sys.account.captchaOnOff"); + if (StringUtils.isEmpty(captchaOnOff)) + { + return true; + } + return Convert.toBool(captchaOnOff); + } + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + @Override + public List 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) + { + 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 configsList = configMapper.selectConfigList(new SysConfig()); + for (SysConfig config : configsList) + { + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + } + + /** + * 清空参数缓存数据 + */ + @Override + public void clearConfigCache() + { + Collection keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*"); + redisCache.deleteObject(keys); + } + + /** + * 重置参数缓存数据 + */ + @Override + public void resetConfigCache() + { + clearConfigCache(); + loadingConfigCache(); + } + + /** + * 校验参数键名是否唯一 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public String 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/salpa-system/src/main/java/com/salpa/system/service/impl/SysDeptServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..12e9fe6 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,329 @@ +package com.salpa.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.salpa.common.annotation.DataScope; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.domain.TreeSelect; +import com.salpa.common.core.domain.entity.SysDept; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.core.text.Convert; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.spring.SpringUtils; +import com.salpa.system.mapper.SysDeptMapper; +import com.salpa.system.mapper.SysRoleMapper; +import com.salpa.system.service.ISysDeptService; + +/** + * 部门管理 服务实现 + * + * @author salpa + */ +@Service +public class SysDeptServiceImpl implements ISysDeptService +{ + @Autowired + private SysDeptMapper deptMapper; + + @Autowired + private SysRoleMapper roleMapper; + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + @Override + @DataScope(deptAlias = "d") + public List selectDeptList(SysDept dept) + { + return deptMapper.selectDeptList(dept); + } + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + @Override + public List buildDeptTree(List depts) + { + List returnList = new ArrayList(); + List tempList = new ArrayList(); + for (SysDept dept : depts) + { + tempList.add(dept.getDeptId()); + } + 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 buildDeptTreeSelect(List depts) + { + List deptTrees = buildDeptTree(depts); + return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List 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 String 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())) + { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + List 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 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 list, SysDept t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysDept tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysDept t) + { + List tlist = new ArrayList(); + Iterator 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 list, SysDept t) + { + return getChildList(list, t).size() > 0; + } +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/impl/SysDictDataServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 0000000..730c87f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,111 @@ +package com.salpa.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.salpa.common.core.domain.entity.SysDictData; +import com.salpa.common.utils.DictUtils; +import com.salpa.system.mapper.SysDictDataMapper; +import com.salpa.system.service.ISysDictDataService; + +/** + * 字典 业务层处理 + * + * @author salpa + */ +@Service +public class SysDictDataServiceImpl implements ISysDictDataService +{ + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + @Override + public List 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 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 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 dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/impl/SysDictTypeServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 0000000..0133017 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,223 @@ +package com.salpa.system.service.impl; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.domain.entity.SysDictData; +import com.salpa.common.core.domain.entity.SysDictType; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.DictUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.mapper.SysDictDataMapper; +import com.salpa.system.mapper.SysDictTypeMapper; +import com.salpa.system.service.ISysDictTypeService; + +/** + * 字典 业务层处理 + * + * @author salpa + */ +@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 selectDictTypeList(SysDictType dictType) + { + return dictTypeMapper.selectDictTypeList(dictType); + } + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeAll() + { + return dictTypeMapper.selectDictTypeAll(); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataByType(String dictType) + { + List 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> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType)); + for (Map.Entry> 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 dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType()); + DictUtils.setDictCache(dict.getDictType(), dictDatas); + } + return row; + } + + /** + * 校验字典类型称是否唯一 + * + * @param dict 字典类型 + * @return 结果 + */ + @Override + public String 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/salpa-system/src/main/java/com/salpa/system/service/impl/SysLogininforServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 0000000..b14f3ea --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,65 @@ +package com.salpa.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.salpa.system.domain.SysLogininfor; +import com.salpa.system.mapper.SysLogininforMapper; +import com.salpa.system.service.ISysLogininforService; + +/** + * 系统访问日志情况信息 服务层处理 + * + * @author salpa + */ +@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 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/salpa-system/src/main/java/com/salpa/system/service/impl/SysMenuServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..5ef2fea --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,517 @@ +package com.salpa.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.salpa.common.constant.Constants; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.domain.TreeSelect; +import com.salpa.common.core.domain.entity.SysMenu; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.domain.vo.MetaVo; +import com.salpa.system.domain.vo.RouterVo; +import com.salpa.system.mapper.SysMenuMapper; +import com.salpa.system.mapper.SysRoleMapper; +import com.salpa.system.mapper.SysRoleMenuMapper; +import com.salpa.system.service.ISysMenuService; + +/** + * 菜单 业务层处理 + * + * @author salpa + */ +@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 selectMenuList(Long userId) + { + return selectMenuList(new SysMenu(), userId); + } + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + @Override + public List selectMenuList(SysMenu menu, Long userId) + { + List 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 selectMenuPermsByUserId(Long userId) + { + List perms = menuMapper.selectMenuPermsByUserId(userId); + Set 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 selectMenuTreeByUserId(Long userId) + { + List 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 selectMenuListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); + } + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + @Override + public List buildMenus(List menus) + { + List routers = new LinkedList(); + 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.setPerms(menu.getPerms()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + List cMenus = menu.getChildren(); + if (!cMenus.isEmpty() && cMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType())) + { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); + } + else if (isMenuFrame(menu)) + { + router.setMeta(null); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponent()); + children.setName(StringUtils.capitalize(menu.getPath())); + children.setPerms(menu.getPerms()); + 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 childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + String routerPath = innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(StringUtils.capitalize(routerPath)); + children.setPerms(menu.getPerms()); + 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 buildMenuTree(List menus) + { + List returnList = new ArrayList(); + List tempList = new ArrayList(); + for (SysMenu dept : menus) + { + tempList.add(dept.getMenuId()); + } + for (Iterator 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 buildMenuTreeSelect(List menus) + { + List 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 String 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) + { + String routerName = StringUtils.capitalize(menu.getPath()); + // 非外链并且是一级目录(类型为目录) + if (isMenuFrame(menu)) + { + routerName = StringUtils.EMPTY; + } + return 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 getChildPerms(List list, int parentId) + { + List returnList = new ArrayList(); + for (Iterator 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 list, SysMenu t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysMenu tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysMenu t) + { + List tlist = new ArrayList(); + Iterator 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 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 }, + new String[] { "", "" }); + } +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/impl/SysNoticeServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 0000000..89d1fd9 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,92 @@ +package com.salpa.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.salpa.system.domain.SysNotice; +import com.salpa.system.mapper.SysNoticeMapper; +import com.salpa.system.service.ISysNoticeService; + +/** + * 公告 服务层实现 + * + * @author salpa + */ +@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 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/salpa-system/src/main/java/com/salpa/system/service/impl/SysOperLogServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 0000000..68f107f --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,76 @@ +package com.salpa.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.salpa.system.domain.SysOperLog; +import com.salpa.system.mapper.SysOperLogMapper; +import com.salpa.system.service.ISysOperLogService; + +/** + * 操作日志 服务层处理 + * + * @author salpa + */ +@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 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/salpa-system/src/main/java/com/salpa/system/service/impl/SysPostServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysPostServiceImpl.java new file mode 100644 index 0000000..5f51ad4 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysPostServiceImpl.java @@ -0,0 +1,178 @@ +package com.salpa.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.domain.SysPost; +import com.salpa.system.mapper.SysPostMapper; +import com.salpa.system.mapper.SysUserPostMapper; +import com.salpa.system.service.ISysPostService; + +/** + * 岗位信息 服务层处理 + * + * @author salpa + */ +@Service +public class SysPostServiceImpl implements ISysPostService +{ + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位信息集合 + */ + @Override + public List selectPostList(SysPost post) + { + return postMapper.selectPostList(post); + } + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + @Override + public List 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 selectPostListByUserId(Long userId) + { + return postMapper.selectPostListByUserId(userId); + } + + /** + * 校验岗位名称是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public String 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 String 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/salpa-system/src/main/java/com/salpa/system/service/impl/SysRoleServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..49d993b --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,424 @@ +package com.salpa.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.salpa.common.annotation.DataScope; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.spring.SpringUtils; +import com.salpa.system.domain.SysRoleDept; +import com.salpa.system.domain.SysRoleMenu; +import com.salpa.system.domain.SysUserRole; +import com.salpa.system.mapper.SysRoleDeptMapper; +import com.salpa.system.mapper.SysRoleMapper; +import com.salpa.system.mapper.SysRoleMenuMapper; +import com.salpa.system.mapper.SysUserRoleMapper; +import com.salpa.system.service.ISysRoleService; + +/** + * 角色 业务层处理 + * + * @author salpa + */ +@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 selectRoleList(SysRole role) + { + return roleMapper.selectRoleList(role); + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List selectRolesByUserId(Long userId) + { + List userRoles = roleMapper.selectRolePermissionByUserId(userId); + List 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 selectRolePermissionByUserId(Long userId) + { + List perms = roleMapper.selectRolePermissionByUserId(userId); + Set 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 selectRoleAll() + { + return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); + } + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List 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 String 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 String 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 roleId 角色id + */ + @Override + public void checkRoleDataScope(Long roleId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysRole role = new SysRole(); + role.setRoleId(roleId); + List 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 list = new ArrayList(); + 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 list = new ArrayList(); + 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 list = new ArrayList(); + for (Long userId : userIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleMapper.batchUserRole(list); + } +} diff --git a/salpa-system/src/main/java/com/salpa/system/service/impl/SysUserOnlineServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysUserOnlineServiceImpl.java new file mode 100644 index 0000000..a721507 --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysUserOnlineServiceImpl.java @@ -0,0 +1,96 @@ +package com.salpa.system.service.impl; + +import org.springframework.stereotype.Service; +import com.salpa.common.core.domain.model.LoginUser; +import com.salpa.common.utils.StringUtils; +import com.salpa.system.domain.SysUserOnline; +import com.salpa.system.service.ISysUserOnlineService; + +/** + * 在线用户 服务层处理 + * + * @author salpa + */ +@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/salpa-system/src/main/java/com/salpa/system/service/impl/SysUserServiceImpl.java b/salpa-system/src/main/java/com/salpa/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..1ec581e --- /dev/null +++ b/salpa-system/src/main/java/com/salpa/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,540 @@ +package com.salpa.system.service.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.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.salpa.common.annotation.DataScope; +import com.salpa.common.constant.UserConstants; +import com.salpa.common.core.domain.entity.SysRole; +import com.salpa.common.core.domain.entity.SysUser; +import com.salpa.common.exception.ServiceException; +import com.salpa.common.utils.SecurityUtils; +import com.salpa.common.utils.StringUtils; +import com.salpa.common.utils.bean.BeanValidators; +import com.salpa.common.utils.spring.SpringUtils; +import com.salpa.system.domain.SysPost; +import com.salpa.system.domain.SysUserPost; +import com.salpa.system.domain.SysUserRole; +import com.salpa.system.mapper.SysPostMapper; +import com.salpa.system.mapper.SysRoleMapper; +import com.salpa.system.mapper.SysUserMapper; +import com.salpa.system.mapper.SysUserPostMapper; +import com.salpa.system.mapper.SysUserRoleMapper; +import com.salpa.system.service.ISysConfigService; +import com.salpa.system.service.ISysUserService; + +/** + * 用户 业务层处理 + * + * @author salpa + */ +@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 + protected Validator validator; + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUserList(SysUser user) + { + return userMapper.selectUserList(user); + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectAllocatedList(SysUser user) + { + return userMapper.selectAllocatedList(user); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List 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 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 list = postMapper.selectPostsByUserName(userName); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); + } + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + @Override + public String checkUserNameUnique(String userName) + { + int count = userMapper.checkUserNameUnique(userName); + if (count > 0) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public String 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 String 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 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 list = new ArrayList(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 list = new ArrayList(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 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(); + String password = configService.selectConfigByKey("sys.user.initPassword"); + for (SysUser user : userList) + { + try + { + // 验证是否存在这个用户 + SysUser u = userMapper.selectUserByUserName(user.getUserName()); + if (StringUtils.isNull(u)) + { + BeanValidators.validateWithException(validator, user); + user.setPassword(SecurityUtils.encryptPassword(password)); + user.setCreateBy(operName); + this.insertUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 导入成功"); + } + else if (isUpdateSupport) + { + BeanValidators.validateWithException(validator, user); + user.setUpdateBy(operName); + this.updateUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 更新成功"); + } + else + { + failureNum++; + failureMsg.append("
" + failureNum + "、账号 " + user.getUserName() + " 已存在"); + } + } + catch (Exception e) + { + failureNum++; + String msg = "
" + 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/salpa-system/src/main/resources/mapper/model/ModelMapper.xml b/salpa-system/src/main/resources/mapper/model/ModelMapper.xml new file mode 100644 index 0000000..56164d7 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/model/ModelMapper.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + insert into project_model_info( + project_name, + project_description, + json_file + )values( + #{projectName}, + #{projectDescription}, + #{jsonFile} + ) + + + + UPDATE sys_user_role SET project_ids = #{projectIds} WHERE user_id = #{userId} + + + + INSERT INTO sys_user_role(user_id,project_ids) values (#{userId},#{projectIds}) + + + + UPDATE project_model_info SET project_name = #{projectName},project_description = #{projectDescription},json_file = #{jsonFile} + WHERE project_id = #{projectId} + + + + UPDATE project_model_info SET deleted = 1 WHERE project_id = #{projectId} + + + + UPDATE project_model_info SET json_file = #{jsonFile} WHERE project_Name = #{projectName} + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/DataSourcePropertyMapper.xml b/salpa-system/src/main/resources/mapper/subject/DataSourcePropertyMapper.xml new file mode 100644 index 0000000..aa18147 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/DataSourcePropertyMapper.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + insert into monitor_data_source_property( + data_source_id, + data_source_key, + data_source_value + )values( + #{dataSourceId}, + #{dataSourceKey}, + #{dataSourceValue} + ) + + + + update monitor_data_source_property + + data_source_id = #{dataSourceId}, + data_source_key = #{dataSourceKey}, + data_source_value = #{dataSourceValue} + + where id = #{id} + + + + DELETE FROM monitor_data_source_property WHERE data_source_id = #{dataSourceId} + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/DatabaseMapper.xml b/salpa-system/src/main/resources/mapper/subject/DatabaseMapper.xml new file mode 100644 index 0000000..c07c09f --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/DatabaseMapper.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + insert into monitor_data_source( + project_id, + database_name, + schema_name, + database_type, + url, + password, + username + )values( + #{projectId}, + #{databaseName}, + #{schemaName}, + #{databaseType}, + #{url}, + #{password}, + #{username} + ) + + + + update monitor_data_source + + project_id = #{projectId}, + database_name = #{databaseName}, + schema_name = #{schemaName}, + database_type = #{databaseType}, + url = #{url}, + password = #{password}, + username = #{username} + + where id = #{id} + + + + DELETE FROM monitor_data_source WHERE project_id = #{projectId} + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/DatabaseTypeMapper.xml b/salpa-system/src/main/resources/mapper/subject/DatabaseTypeMapper.xml new file mode 100644 index 0000000..3e54b53 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/DatabaseTypeMapper.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into monitor_database_type( + database_type, + icon, + description, + jdbc_driver_file_url, + jdbc_driver_file_path, + jdbc_driver_class_name, + jdbc_protocol, + url_pattern + )values( + #{databaseType}, + #{icon}, + #{description}, + #{jdbcDriverFileUrl}, + #{jdbcDriverFilePath}, + #{jdbcDriverClassName}, + #{jdbcProtocol}, + #{urlPattern} + ) + + + + UPDATE monitor_database_type + + database_type = #{databaseType}, + icon = #{icon}, + description = #{description}, + jdbc_driver_file_url = #{jdbcDriverFileUrl}, + jdbc_driver_file_path = #{jdbcDriverFilePath}, + jdbc_driver_class_name = #{jdbcDriverClassName}, + jdbc_protocol = #{jdbcProtocol}, + url_pattern = #{urlPattern} + + WHERE id = #{id} + + + + UPDATE monitor_database_type SET deleted = 1 WHERE id = #{id} + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/DocumentDescriptionMapper.xml b/salpa-system/src/main/resources/mapper/subject/DocumentDescriptionMapper.xml new file mode 100644 index 0000000..31bd2ce --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/DocumentDescriptionMapper.xml @@ -0,0 +1,54 @@ + + + + + + UPDATE monitor_document_description + + content = #{content}, + update_by = #{updateBy} + + WHERE table_name = #{tableName} AND project_id = #{projectId} + + AND column_name = #{columnName} + + + AND column_name is NULL + + + + + + + insert into monitor_document_description( + project_id, + table_name, + column_name, + content + )values( + #{projectId}, + #{tableName}, + #{columnName}, + #{content} + ) + + + + + + + + diff --git a/salpa-system/src/main/resources/mapper/subject/DocumentDiscussionMapper.xml b/salpa-system/src/main/resources/mapper/subject/DocumentDiscussionMapper.xml new file mode 100644 index 0000000..d049eb5 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/DocumentDiscussionMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + insert into monitor_document_discussion( + id, + content, + user_id, + project_id, + table_name, + column_name, + create_at + )values( + #{content}, + #{userId}, + #{projectId}, + #{tableName}, + #{columnName}, + sysdate() + ) + + + + + + + + + DELETE FROM monitor_document_discussion WHERE id = #{id} + + diff --git a/salpa-system/src/main/resources/mapper/subject/DocumentMapper.xml b/salpa-system/src/main/resources/mapper/subject/DocumentMapper.xml new file mode 100644 index 0000000..e7f5e75 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/DocumentMapper.xml @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM monitor_database_document WHERE id = #{docId} + + + + + + + + + + + + + + + + + + + + + + + + insert into monitor_project_sync_task( + project_id, + user_id, + status + )values( + #{projectId}, + #{userId}, + #{status} + ) + + + + insert into monitor_database_document( + project_id, + database_name, + `schema_name`, + product_name, + product_version, + version + )values( + #{projectId}, + #{databaseName}, + #{schemaName}, + #{productName}, + #{productVersion}, + #{documentVersion} + ) + + + + insert into monitor_table_document( + database_document_id, + `name`, + comment, + `type` + )values( + #{databaseDocumentId}, + #{name}, + #{comment}, + #{type} + ) + + + + UPDATE monitor_project_sync_task SET status = "CANCELED",result = "主动取消" + WHERE id = #{taskId} + + + + UPDATE monitor_database_document SET is_archive = #{archive} + WHERE id = #{id} + + + + UPDATE monitor_database_document SET is_archive = 0 + WHERE project_id = #{projectId} AND version = #{version} + + + + UPDATE monitor_project_sync_task SET run_at = sysdate() WHERE id = #{syncTask} + + + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/DocumentTemplatePropertyMapper.xml b/salpa-system/src/main/resources/mapper/subject/DocumentTemplatePropertyMapper.xml new file mode 100644 index 0000000..3efa830 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/DocumentTemplatePropertyMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/OperationLogMapper.xml b/salpa-system/src/main/resources/mapper/subject/OperationLogMapper.xml new file mode 100644 index 0000000..9a95f21 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/OperationLogMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + insert into monitor_operation_log( + operator_user_id, + operator_username, + operator_nickname, + operation_module, + operation_code, + operation_name, + operation_response, + is_success, + involved_project_id + )values( + #{operatorUserId}, + #{operatorUsername}, + #{operatorNickname}, + #{operationModule}, + #{operationCode}, + #{operationName}, + #{operationResponse}, + #{success}, + #{involvedProjectId} + ) + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/ProjectMapper.xml b/salpa-system/src/main/resources/mapper/subject/ProjectMapper.xml new file mode 100644 index 0000000..76d37f4 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/ProjectMapper.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + insert into monitor_project( + subject_id, + project_name, + project_description, + deleted + )values( + #{subjectId}, + #{projectName}, + #{projectDescription}, + 0 + ) + + + + update monitor_project + + project_name = #{projectName}, + project_description = #{projectDescription}, + subject_id = #{subjectId} + + where id = #{id} + + + + UPDATE monitor_project SET deleted = 1 WHERE id = #{id} + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/ProjectSyncRuleMapper.xml b/salpa-system/src/main/resources/mapper/subject/ProjectSyncRuleMapper.xml new file mode 100644 index 0000000..c1be9e8 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/ProjectSyncRuleMapper.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + insert into monitor_project_sync_rule( + project_id, + ignore_table_name_regex_array, + ignore_column_name_regex_array, + auto_sync_cron, + is_auto_sync + )values( + #{projectSyncRule.projectId}, + #{tableName}, + #{columnName}, + #{projectSyncRule.autoSyncCron}, + #{projectSyncRule.autoSync} + ) + + + + DELETE FROM monitor_project_sync_rule WHERE project_id = #{id} + + + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/ProjectSyncTaskMapper.xml b/salpa-system/src/main/resources/mapper/subject/ProjectSyncTaskMapper.xml new file mode 100644 index 0000000..2d910a9 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/ProjectSyncTaskMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + UPDATE monitor_project_sync_task SET status = #{status},result = #{result},run_at = sysdate() + WHERE id = #{taskId} + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/SubjectMapper.xml b/salpa-system/src/main/resources/mapper/subject/SubjectMapper.xml new file mode 100644 index 0000000..3863181 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/SubjectMapper.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + insert into monitor_subject( + subject_name, + subject_description, + deleted + )values( + #{subjectName}, + #{subjectDescription}, + 0 + ) + + + + UPDATE monitor_subject SET subject_name = #{subjectName},subject_description = #{subjectDescription} + WHERE id = #{id} + + + + UPDATE monitor_subject SET deleted = 1 WHERE id = #{id} + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/TableColumnDocumentMapper.xml b/salpa-system/src/main/resources/mapper/subject/TableColumnDocumentMapper.xml new file mode 100644 index 0000000..93ad4b2 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/TableColumnDocumentMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into monitor_table_column_document( + table_document_id, + `name`, + `type`, + comment, + decimal_digits, + default_value, + data_type, + `size`, + is_primary_key, + `nullable`, + auto_increment, + database_document_id + )values( + #{tableDocumentId}, + #{name}, + #{type}, + #{comment}, + #{decimalDigits}, + #{defaultValue}, + #{dataType}, + #{size}, + #{primaryKey}, + #{nullable}, + #{autoIncrement}, + #{databaseDocumentId} + ) + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/TableForeignKeyDocumentMapper.xml b/salpa-system/src/main/resources/mapper/subject/TableForeignKeyDocumentMapper.xml new file mode 100644 index 0000000..a27cb43 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/TableForeignKeyDocumentMapper.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + insert into monitor_table_foreign_key_document( + table_document_id, + key_seq, + fk_name, + fk_table_name, + fk_column_name, + pk_name, + pk_table_name, + pk_column_name, + update_rule, + delete_rule, + database_document_id + )values( + #{tableDocumentId}, + #{keySeq}, + #{fkName}, + #{fkTableName}, + #{fkColumnName}, + #{pkName}, + #{pkTableName}, + #{pkColumnName}, + #{updateRule}, + #{deleteRule}, + #{databaseDocumentId} + ) + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/TableIndexDocumentMapper.xml b/salpa-system/src/main/resources/mapper/subject/TableIndexDocumentMapper.xml new file mode 100644 index 0000000..4bf5ba9 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/TableIndexDocumentMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + insert into monitor_table_index_document( + table_document_id, + `name`, + is_unique, + column_name_array, + database_document_id + )values( + #{tableDocumentId}, + #{name}, + #{unique}, + #{columnNameArray}, + #{databaseDocumentId} + ) + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/TableTriggerDocumentMapper.xml b/salpa-system/src/main/resources/mapper/subject/TableTriggerDocumentMapper.xml new file mode 100644 index 0000000..c223d84 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/TableTriggerDocumentMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + insert into monitor_table_trigger_document( + table_document_id, + `name`, + timing, + manipulation, + statement, + trigger_create_at, + database_document_id + )values( + #{tableDocumentId}, + #{name}, + #{timing}, + #{manipulation}, + #{statement}, + #{triggerCreateAt}, + #{databaseDocumentId} + ) + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/subject/WarningMapper.xml b/salpa-system/src/main/resources/mapper/subject/WarningMapper.xml new file mode 100644 index 0000000..d9c4f34 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/subject/WarningMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + UPDATE monitor_warning SET warning_status = 1,handling_user = #{userName} + WHERE id = #{id} + + + + INSERT INTO monitor_warning(warning_name,warning_subject,warning_data_source,warning_time,warning_content) + VALUES (#{warningName},#{warningSubject},#{warningDataSource},#{warningTime},#{warningContent}) + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysConfigMapper.xml b/salpa-system/src/main/resources/mapper/system/SysConfigMapper.xml new file mode 100644 index 0000000..9b40699 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config + + + + + + + and config_id = #{configId} + + + and config_key = #{configKey} + + + + + + + + + + + + insert into sys_config ( + config_name, + config_key, + config_value, + config_type, + create_by, + remark, + create_time + )values( + #{configName}, + #{configKey}, + #{configValue}, + #{configType}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_config + + config_name = #{configName}, + config_key = #{configKey}, + config_value = #{configValue}, + config_type = #{configType}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where config_id = #{configId} + + + + delete from sys_config where config_id = #{configId} + + + + delete from sys_config where config_id in + + #{configId} + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysDeptMapper.xml b/salpa-system/src/main/resources/mapper/system/SysDeptMapper.xml new file mode 100644 index 0000000..9a9fe00 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + + + + + + + insert into sys_dept( + dept_id, + parent_id, + dept_name, + ancestors, + order_num, + leader, + phone, + email, + status, + create_by, + create_time + )values( + #{deptId}, + #{parentId}, + #{deptName}, + #{ancestors}, + #{orderNum}, + #{leader}, + #{phone}, + #{email}, + #{status}, + #{createBy}, + sysdate() + ) + + + + update sys_dept + + parent_id = #{parentId}, + dept_name = #{deptName}, + ancestors = #{ancestors}, + order_num = #{orderNum}, + leader = #{leader}, + phone = #{phone}, + email = #{email}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where dept_id = #{deptId} + + + + update sys_dept set ancestors = + + when #{item.deptId} then #{item.ancestors} + + where dept_id in + + #{item.deptId} + + + + + update sys_dept set status = '0' where dept_id in + + #{deptId} + + + + + update sys_dept set del_flag = '2' where dept_id = #{deptId} + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/salpa-system/src/main/resources/mapper/system/SysDictDataMapper.xml new file mode 100644 index 0000000..4134f55 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + delete from sys_dict_data where dict_code = #{dictCode} + + + + delete from sys_dict_data where dict_code in + + #{dictCode} + + + + + update sys_dict_data + + dict_sort = #{dictSort}, + dict_label = #{dictLabel}, + dict_value = #{dictValue}, + dict_type = #{dictType}, + css_class = #{cssClass}, + list_class = #{listClass}, + is_default = #{isDefault}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_code = #{dictCode} + + + + update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} + + + + insert into sys_dict_data( + dict_sort, + dict_label, + dict_value, + dict_type, + css_class, + list_class, + is_default, + status, + remark, + create_by, + create_time + )values( + #{dictSort}, + #{dictLabel}, + #{dictValue}, + #{dictType}, + #{cssClass}, + #{listClass}, + #{isDefault}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/salpa-system/src/main/resources/mapper/system/SysDictTypeMapper.xml new file mode 100644 index 0000000..f52cd2c --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysDictTypeMapper.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + select dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type + + + + + + + + + + + + + + delete from sys_dict_type where dict_id = #{dictId} + + + + delete from sys_dict_type where dict_id in + + #{dictId} + + + + + update sys_dict_type + + dict_name = #{dictName}, + dict_type = #{dictType}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_id = #{dictId} + + + + insert into sys_dict_type( + dict_name, + dict_type, + status, + remark, + create_by, + create_time + )values( + #{dictName}, + #{dictType}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/salpa-system/src/main/resources/mapper/system/SysLogininforMapper.xml new file mode 100644 index 0000000..93ffc77 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + insert into sys_logininfor (user_name, status, ipaddr, login_location, browser, os, msg, login_time) + values (#{userName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate()) + + + + + + delete from sys_logininfor where info_id in + + #{infoId} + + + + + truncate table sys_logininfor + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysMenuMapper.xml b/salpa-system/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..779ee18 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select menu_id, menu_name, parent_id, order_num, path, component, `query`, is_frame, is_cache, menu_type, visible, status, ifnull(perms,'') as perms, icon, create_time + from sys_menu + + + + + + + + + + + + + + + + + + + + + + + + update sys_menu + + menu_name = #{menuName}, + parent_id = #{parentId}, + order_num = #{orderNum}, + path = #{path}, + component = #{component}, + `query` = #{query}, + is_frame = #{isFrame}, + is_cache = #{isCache}, + menu_type = #{menuType}, + visible = #{visible}, + status = #{status}, + perms = #{perms}, + icon = #{icon}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where menu_id = #{menuId} + + + + insert into sys_menu( + menu_id, + parent_id, + menu_name, + order_num, + path, + component, + `query`, + is_frame, + is_cache, + menu_type, + visible, + status, + perms, + icon, + remark, + create_by, + create_time + )values( + #{menuId}, + #{parentId}, + #{menuName}, + #{orderNum}, + #{path}, + #{component}, + #{query}, + #{isFrame}, + #{isCache}, + #{menuType}, + #{visible}, + #{status}, + #{perms}, + #{icon}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_menu where menu_id = #{menuId} + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/salpa-system/src/main/resources/mapper/system/SysNoticeMapper.xml new file mode 100644 index 0000000..9e7e093 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + insert into sys_notice ( + notice_title, + notice_type, + notice_content, + status, + remark, + create_by, + create_time + )values( + #{noticeTitle}, + #{noticeType}, + #{noticeContent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_notice + + notice_title = #{noticeTitle}, + notice_type = #{noticeType}, + notice_content = #{noticeContent}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id in + + #{noticeId} + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/salpa-system/src/main/resources/mapper/system/SysOperLogMapper.xml new file mode 100644 index 0000000..dff4f98 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 + from sys_oper_log + + + + 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, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, sysdate()) + + + + + + delete from sys_oper_log where oper_id in + + #{operId} + + + + + + + truncate table sys_oper_log + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysPostMapper.xml b/salpa-system/src/main/resources/mapper/system/SysPostMapper.xml new file mode 100644 index 0000000..a1beb1c --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post + + + + + + + + + + + + + + + + + + update sys_post + + post_code = #{postCode}, + post_name = #{postName}, + post_sort = #{postSort}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where post_id = #{postId} + + + + insert into sys_post( + post_id, + post_code, + post_name, + post_sort, + status, + remark, + create_by, + create_time + )values( + #{postId}, + #{postCode}, + #{postName}, + #{postSort}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_post where post_id = #{postId} + + + + delete from sys_post where post_id in + + #{postId} + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/salpa-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 0000000..d078a08 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_role_dept where role_id=#{roleId} + + + + + + delete from sys_role_dept where role_id in + + #{roleId} + + + + + insert into sys_role_dept(role_id, dept_id) values + + (#{item.roleId},#{item.deptId}) + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysRoleMapper.xml b/salpa-system/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..d4ad5c4 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + + + + + + + insert into sys_role( + role_id, + role_name, + role_key, + role_sort, + data_scope, + menu_check_strictly, + dept_check_strictly, + status, + remark, + create_by, + create_time + )values( + #{roleId}, + #{roleName}, + #{roleKey}, + #{roleSort}, + #{dataScope}, + #{menuCheckStrictly}, + #{deptCheckStrictly}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_role + + role_name = #{roleName}, + role_key = #{roleKey}, + role_sort = #{roleSort}, + data_scope = #{dataScope}, + menu_check_strictly = #{menuCheckStrictly}, + dept_check_strictly = #{deptCheckStrictly}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id in + + #{roleId} + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/salpa-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..d9bd72f --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + delete from sys_role_menu where role_id=#{roleId} + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + insert into sys_role_menu(role_id, menu_id) values + + (#{item.roleId},#{item.menuId}) + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysUserMapper.xml b/salpa-system/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..fdb4053 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + create_by, + remark, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{status}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_user + + dept_id = #{deptId}, + user_name = #{userName}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where user_id = #{userId} + + + + update sys_user set status = #{status} where user_id = #{userId} + + + + update sys_user set avatar = #{avatar} where user_name = #{userName} + + + + update sys_user set password = #{password} where user_name = #{userName} + + + + update sys_user set del_flag = '2' where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id in + + #{userId} + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/salpa-system/src/main/resources/mapper/system/SysUserPostMapper.xml new file mode 100644 index 0000000..0b66721 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysUserPostMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_user_post where user_id=#{userId} + + + + + + delete from sys_user_post where user_id in + + #{userId} + + + + + insert into sys_user_post(user_id, post_id) values + + (#{item.userId},#{item.postId}) + + + + \ No newline at end of file diff --git a/salpa-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/salpa-system/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..024ade8 --- /dev/null +++ b/salpa-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + delete from sys_user_role where user_id=#{userId} + + + + + + + + delete from sys_user_role where user_id in + + #{userId} + + + + + insert into sys_user_role(user_id, role_id) values + + (#{item.userId},#{item.roleId}) + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + \ No newline at end of file diff --git a/sql/metaops.sql b/sql/metaops.sql new file mode 100644 index 0000000..048b246 --- /dev/null +++ b/sql/metaops.sql @@ -0,0 +1,1130 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for gen_table +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table`; +CREATE TABLE `gen_table` ( + `table_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表名称', + `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表描述', + `sub_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联子表的表名', + `sub_table_fk_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '子表关联的外键名', + `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '实体类名称', + `tpl_category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', + `package_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成包路径', + `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模块名', + `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成业务名', + `function_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能名', + `function_author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能作者', + `gen_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', + `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', + `options` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '其它生成选项', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`table_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of gen_table +-- ---------------------------- + +-- ---------------------------- +-- Table structure for gen_table_column +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table_column`; +CREATE TABLE `gen_table_column` ( + `column_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '归属表编号', + `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列名称', + `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列描述', + `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列类型', + `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA类型', + `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA字段名', + `is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否主键(1是)', + `is_increment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否自增(1是)', + `is_required` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否必填(1是)', + `is_insert` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否为插入字段(1是)', + `is_edit` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否编辑字段(1是)', + `is_list` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否列表字段(1是)', + `is_query` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否查询字段(1是)', + `query_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', + `html_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `sort` int(11) NULL DEFAULT NULL COMMENT '排序', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`column_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表字段' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of gen_table_column +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_data_source +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_data_source`; +CREATE TABLE `monitor_data_source` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `project_id` int(11) NOT NULL COMMENT '模型id', + `database_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据库名称', + `schema_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '概要名称', + `database_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据库类型', + `url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '地址', + `username` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '账号', + `password` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码', + `update_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_project_id`(`project_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-数据源表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_data_source +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_data_source_property +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_data_source_property`; +CREATE TABLE `monitor_data_source_property` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_source_id` int(11) NOT NULL COMMENT '数据源id', + `data_source_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '参数名称', + `data_source_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '参数值', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_data_source_id`(`data_source_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-数据源配置表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_data_source_property +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_database_document +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_database_document`; +CREATE TABLE `monitor_database_document` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `project_id` int(11) NOT NULL COMMENT '模型id', + `database_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据库名称', + `schema_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '概要名称', + `product_name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品名称', + `product_version` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品版本', + `version` bigint(20) NOT NULL DEFAULT 1 COMMENT '版本', + `is_archive` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否存档', + `update_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_project_id_version_is_archive`(`project_id`, `version`, `is_archive`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-数据源文档存储表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_database_document +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_database_type +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_database_type`; +CREATE TABLE `monitor_database_type` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `database_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据库类型', + `icon` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '图标地址', + `description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '描述', + `jdbc_driver_file_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '驱动下载地址', + `jdbc_driver_file_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '驱动存放路径', + `jdbc_driver_class_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '驱动类名称', + `jdbc_protocol` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'jdbc协议', + `url_pattern` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'url生成表达式', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标识', + `deleted_token` int(11) NOT NULL DEFAULT 0 COMMENT '删除token', + `update_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_database_type_deleted_deleted_token`(`database_type`, `deleted`, `deleted_token`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 43 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-数据库类型表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_database_type +-- ---------------------------- +INSERT INTO `monitor_database_type` VALUES (4, 'mysql', '/static/database/MySQL.svg', 'mysql数据库', 'https://maven.aliyun.com/repository/central/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar', NULL, 'com.mysql.cj.jdbc.Driver', 'jdbc:mysql', '{{jdbc.protocol}}://{{db.url}}/{{db.name}}', 0, 0, '2022-09-22 16:19:18', '2022-08-15 18:27:29'); +INSERT INTO `monitor_database_type` VALUES (33, 'mariadb-3.0.3', '/static/database/MariaDB.svg', 'mariadb数据库', 'https://maven.aliyun.com/repository/central/org/mariadb/jdbc/mariadb-java-client/3.0.3/mariadb-java-client-3.0.3.jar', NULL, 'org.mariadb.jdbc.Driver', 'jdbc:mariadb', '{{jdbc.protocol}}://{{db.url}}/{{db.name}}', 0, 0, '2022-09-22 15:11:51', '2022-09-22 14:27:51'); + +-- ---------------------------- +-- Table structure for monitor_document_description +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_document_description`; +CREATE TABLE `monitor_document_description` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '描述内容', + `project_id` int(11) NOT NULL COMMENT '模型id', + `table_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表名', + `column_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字段名', + `update_by` bigint(11) NOT NULL COMMENT '修改者', + `update_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_project_id_table_name_column_name`(`project_id`, `table_name`, `column_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-文档描述表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_document_description +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_document_discussion +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_document_discussion`; +CREATE TABLE `monitor_document_discussion` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '评论内容', + `user_id` bigint(11) NOT NULL COMMENT '评论人', + `project_id` int(11) NOT NULL COMMENT '模型id', + `table_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表名', + `column_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字段名', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_project_id`(`project_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-文档评论表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_document_discussion +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_document_template_property +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_document_template_property`; +CREATE TABLE `monitor_document_template_property` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `pro_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '特性键', + `pro_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '特性值', + `default_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '默认值', + `type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_type_key`(`type`, `pro_key`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-文档模板特性' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_document_template_property +-- ---------------------------- +INSERT INTO `monitor_document_template_property` VALUES (1, 'name', NULL, '名称', 'TABLE_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (2, 'type', NULL, '类型', 'TABLE_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (3, 'comment', NULL, '注释', 'TABLE_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (4, 'description', NULL, '描述', 'TABLE_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (5, 'name', NULL, '名称', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (6, 'type', NULL, '类型', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (7, 'size', NULL, '长度', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (8, 'decimalDigits', NULL, '浮点精度', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (9, 'comment', NULL, '注释', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (10, 'description', NULL, '描述', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (11, 'isPrimaryKey', NULL, '主键', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (12, 'nullable', NULL, '可空', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (13, 'autoIncrement', NULL, '自增', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (14, 'defaultValue', NULL, '默认值', 'COLUMN_FIELD_NAME', '2022-07-08 04:00:19'); +INSERT INTO `monitor_document_template_property` VALUES (15, 'name', NULL, '名称', 'INDEX_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (16, 'isUnique', NULL, '唯一', 'INDEX_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (17, 'columnNames', NULL, '列名', 'INDEX_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (18, 'fkName', NULL, '外键名', 'FOREIGN_KEY_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (19, 'fkTableName', NULL, '外键表名', 'FOREIGN_KEY_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (20, 'fkColumnName', NULL, '外键列名', 'FOREIGN_KEY_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (21, 'pkName', NULL, '主键名', 'FOREIGN_KEY_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (22, 'pkTableName', NULL, '主键表名', 'FOREIGN_KEY_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (23, 'pkColumnName', NULL, '主键列名', 'FOREIGN_KEY_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (24, 'updateRule', NULL, '更新规则', 'FOREIGN_KEY_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (25, 'deleteRule', NULL, '删除规则', 'FOREIGN_KEY_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (26, 'name', NULL, '名称', 'TRIGGER_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (27, 'timing', NULL, '触发时机', 'TRIGGER_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (28, 'manipulation', NULL, '触发器', 'TRIGGER_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (29, 'statement', NULL, '表达式', 'TRIGGER_FIELD_NAME', '2022-07-08 04:00:20'); +INSERT INTO `monitor_document_template_property` VALUES (30, 'triggerCreateAt', NULL, '创建时间', 'TRIGGER_FIELD_NAME', '2022-07-08 04:00:20'); + +-- ---------------------------- +-- Table structure for monitor_operation_log +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_operation_log`; +CREATE TABLE `monitor_operation_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `operator_user_id` bigint(20) NOT NULL COMMENT '运行者id', + `operator_username` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '运行人名称', + `operator_nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '运行人昵称', + `operation_module` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模块', + `operation_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'code值', + `operation_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '方法名', + `operation_response` json NOT NULL COMMENT '运行结果', + `is_success` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否成功', + `involved_project_id` int(11) NULL DEFAULT NULL COMMENT '模型id', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-同步日志表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_operation_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_project +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_project`; +CREATE TABLE `monitor_project` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `project_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '项目名称', + `project_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '项目描述', + `subject_id` int(11) NOT NULL COMMENT '主题id', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标识', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 72 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '模型监控-模型表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_project +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_project_sync_rule +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_project_sync_rule`; +CREATE TABLE `monitor_project_sync_rule` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `project_id` int(11) NOT NULL COMMENT '模型id', + `ignore_table_name_regex_array` json NOT NULL COMMENT '忽略表数组', + `ignore_column_name_regex_array` json NOT NULL COMMENT '忽略字段数组', + `is_auto_sync` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否自动同步', + `auto_sync_cron` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '同步规则', + `update_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_project_id`(`project_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-模型同步规则表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_project_sync_rule +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_project_sync_task +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_project_sync_task`; +CREATE TABLE `monitor_project_sync_task` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `project_id` int(11) NOT NULL COMMENT '模型id', + `user_id` bigint(11) NOT NULL COMMENT '执行人', + `status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NEW' COMMENT '状态NEW RUNNING FINISHED FAILED CANCELED', + `result` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '结果', + `run_at` datetime(0) NULL DEFAULT NULL COMMENT '开始时间', + `update_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_project_id`(`project_id`) USING BTREE, + INDEX `idx_user_id`(`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-模型同步任务表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_project_sync_task +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_subject +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_subject`; +CREATE TABLE `monitor_subject` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `subject_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称', + `subject_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '描述', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标识', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 49 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '模型监控-主题表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_subject +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_table_column_document +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_table_column_document`; +CREATE TABLE `monitor_table_column_document` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `table_document_id` int(11) NOT NULL COMMENT '表文档id', + `database_document_id` int(11) NOT NULL COMMENT '数据库文档id', + `name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '字段名', + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型', + `data_type` int(11) NOT NULL DEFAULT 99999 COMMENT '数据类型', + `comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '注释', + `default_value` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '默认值', + `size` int(11) NOT NULL COMMENT '长度', + `decimal_digits` int(11) NULL DEFAULT NULL COMMENT '精度', + `is_primary_key` tinyint(1) NOT NULL COMMENT '是否主键', + `nullable` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否为空YES, NO, UNKNOWN', + `auto_increment` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否自增YES, NO, UNKNOWN', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_table_document_id`(`table_document_id`) USING BTREE, + INDEX `idx_database_document_id`(`database_document_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-表列文档' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_table_column_document +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_table_document +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_table_document`; +CREATE TABLE `monitor_table_document` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `database_document_id` int(11) NOT NULL COMMENT '数据库文档id', + `name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表名', + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型', + `comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '注释', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_database_document_id`(`database_document_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-表文档' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_table_document +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_table_foreign_key_document +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_table_foreign_key_document`; +CREATE TABLE `monitor_table_foreign_key_document` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `table_document_id` int(11) NOT NULL COMMENT '表文档id', + `database_document_id` int(11) NOT NULL COMMENT '数据库文档id', + `key_seq` int(11) NOT NULL DEFAULT 0 COMMENT '键顺序', + `fk_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '外键名称', + `fk_table_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '外键表名', + `fk_column_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '外键字段名', + `pk_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主键名称', + `pk_table_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键表名', + `pk_column_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键字段名', + `update_rule` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '更新规则NO_ACTION, CASCADE, SET_NULL, SET_DEFAULT', + `delete_rule` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '删除规则NO_ACTION, CASCADE, SET_NULL, SET_DEFAULT', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_table_document_id`(`table_document_id`) USING BTREE, + INDEX `idx_database_document_id`(`database_document_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-表外键文档' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_table_foreign_key_document +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_table_index_document +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_table_index_document`; +CREATE TABLE `monitor_table_index_document` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `table_document_id` int(11) NOT NULL COMMENT '表文档id', + `database_document_id` int(11) NOT NULL COMMENT '数据库文档id', + `name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称', + `is_unique` tinyint(1) NOT NULL COMMENT '是否唯一', + `column_name_array` json NOT NULL COMMENT '字段名数组', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_table_document_id`(`table_document_id`) USING BTREE, + INDEX `idx_database_document_id`(`database_document_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-表索引文档' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_table_index_document +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_table_trigger_document +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_table_trigger_document`; +CREATE TABLE `monitor_table_trigger_document` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `name` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称', + `table_document_id` int(11) NOT NULL COMMENT '表文档id', + `database_document_id` int(11) NOT NULL COMMENT '数据库文档id', + `timing` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '时机', + `manipulation` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作', + `statement` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '声明', + `trigger_create_at` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发时间', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_table_document_id`(`table_document_id`) USING BTREE, + INDEX `idx_database_document_id`(`database_document_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模型监控-表触发文档' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of monitor_table_trigger_document +-- ---------------------------- + +-- ---------------------------- +-- Table structure for monitor_warning +-- ---------------------------- +DROP TABLE IF EXISTS `monitor_warning`; +CREATE TABLE `monitor_warning` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `warning_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '告警名称', + `warning_subject` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所属主题模型', + `warning_data_source` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所属数据库', + `warning_time` datetime(0) NOT NULL COMMENT '告警时间', + `warning_content` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '告警内容', + `warning_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '告警状态', + `handling_user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '处理人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '告警管理表' ROW_FORMAT = DYNAMIC; + + +-- ---------------------------- +-- Table structure for project_model_info +-- ---------------------------- +DROP TABLE IF EXISTS `project_model_info`; +CREATE TABLE `project_model_info` ( + `project_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '项目编号', + `project_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '项目名称', + `project_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '项目描述', + `json_file` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '项目json文件存储', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标识', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + PRIMARY KEY (`project_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '项目模型信息管理表' ROW_FORMAT = DYNAMIC; + + + + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `config_id` int(5) NOT NULL AUTO_INCREMENT COMMENT '参数主键', + `config_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数名称', + `config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键名', + `config_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键值', + `config_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '参数配置表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_config +-- ---------------------------- +INSERT INTO `sys_config` VALUES (1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', '2022-08-05 01:11:18', '', NULL, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); +INSERT INTO `sys_config` VALUES (2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', '2022-08-05 01:11:18', '', NULL, '初始化密码 123456'); +INSERT INTO `sys_config` VALUES (3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', '2022-08-05 01:11:18', '', NULL, '深色主题theme-dark,浅色主题theme-light'); +INSERT INTO `sys_config` VALUES (4, '账号自助-验证码开关', 'sys.account.captchaOnOff', 'true', 'Y', 'admin', '2022-08-05 01:11:18', 'admin', '2022-10-21 13:31:53', '是否开启验证码功能(true开启,false关闭)'); +INSERT INTO `sys_config` VALUES (5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', '2022-08-05 01:11:18', '', NULL, '是否开启注册用户功能(true开启,false关闭)'); + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `dept_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '部门id', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父部门id', + `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表', + `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', + `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`dept_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 110 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +INSERT INTO `sys_dept` VALUES (100, 0, '0', '新科技', 0, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', 'admin', '2022-10-21 10:08:22'); +INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '深圳总公司', 1, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); +INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '长沙分公司', 2, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); +INSERT INTO `sys_dept` VALUES (103, 101, '0,100,101', '研发部门', 1, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); +INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); +INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); +INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); +INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); +INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); +INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, 'admin', '15888888888', 'admin@qq.com', '0', '0', 'admin', '2022-08-05 01:10:47', '', NULL); + +-- ---------------------------- +-- Table structure for sys_dict_data +-- ---------------------------- +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) NULL DEFAULT 0 COMMENT '字典排序', + `dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典标签', + `dict_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典键值', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)', + `list_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表格回显样式', + `is_default` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否默认(Y是 N否)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_code`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_dict_data +-- ---------------------------- +INSERT INTO `sys_dict_data` VALUES (1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '性别男'); +INSERT INTO `sys_dict_data` VALUES (2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '性别女'); +INSERT INTO `sys_dict_data` VALUES (3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '性别未知'); +INSERT INTO `sys_dict_data` VALUES (4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '显示菜单'); +INSERT INTO `sys_dict_data` VALUES (5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '隐藏菜单'); +INSERT INTO `sys_dict_data` VALUES (6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '默认分组'); +INSERT INTO `sys_dict_data` VALUES (11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '系统分组'); +INSERT INTO `sys_dict_data` VALUES (12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '系统默认是'); +INSERT INTO `sys_dict_data` VALUES (13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '系统默认否'); +INSERT INTO `sys_dict_data` VALUES (14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '通知'); +INSERT INTO `sys_dict_data` VALUES (15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '公告'); +INSERT INTO `sys_dict_data` VALUES (16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '关闭状态'); +INSERT INTO `sys_dict_data` VALUES (18, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '新增操作'); +INSERT INTO `sys_dict_data` VALUES (19, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '修改操作'); +INSERT INTO `sys_dict_data` VALUES (20, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '删除操作'); +INSERT INTO `sys_dict_data` VALUES (21, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '授权操作'); +INSERT INTO `sys_dict_data` VALUES (22, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '导出操作'); +INSERT INTO `sys_dict_data` VALUES (23, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '导入操作'); +INSERT INTO `sys_dict_data` VALUES (24, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '强退操作'); +INSERT INTO `sys_dict_data` VALUES (25, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '生成操作'); +INSERT INTO `sys_dict_data` VALUES (26, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '清空操作'); +INSERT INTO `sys_dict_data` VALUES (27, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (28, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', '2022-08-05 01:11:15', '', NULL, '停用状态'); + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +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) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典名称', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_id`) USING BTREE, + UNIQUE INDEX `dict_type`(`dict_type`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典类型表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_dict_type +-- ---------------------------- +INSERT INTO `sys_dict_type` VALUES (1, '用户性别', 'sys_user_sex', '0', 'admin', '2022-08-05 01:11:12', '', NULL, '用户性别列表'); +INSERT INTO `sys_dict_type` VALUES (2, '菜单状态', 'sys_show_hide', '0', 'admin', '2022-08-05 01:11:12', '', NULL, '菜单状态列表'); +INSERT INTO `sys_dict_type` VALUES (3, '系统开关', 'sys_normal_disable', '0', 'admin', '2022-08-05 01:11:12', '', NULL, '系统开关列表'); +INSERT INTO `sys_dict_type` VALUES (4, '任务状态', 'sys_job_status', '0', 'admin', '2022-08-05 01:11:12', '', NULL, '任务状态列表'); +INSERT INTO `sys_dict_type` VALUES (5, '任务分组', 'sys_job_group', '0', 'admin', '2022-08-05 01:11:12', '', NULL, '任务分组列表'); +INSERT INTO `sys_dict_type` VALUES (6, '系统是否', 'sys_yes_no', '0', 'admin', '2022-08-05 01:11:12', '', NULL, '系统是否列表'); +INSERT INTO `sys_dict_type` VALUES (7, '通知类型', 'sys_notice_type', '0', 'admin', '2022-08-05 01:11:13', '', NULL, '通知类型列表'); +INSERT INTO `sys_dict_type` VALUES (8, '通知状态', 'sys_notice_status', '0', 'admin', '2022-08-05 01:11:13', '', NULL, '通知状态列表'); +INSERT INTO `sys_dict_type` VALUES (9, '操作类型', 'sys_oper_type', '0', 'admin', '2022-08-05 01:11:13', '', NULL, '操作类型列表'); +INSERT INTO `sys_dict_type` VALUES (10, '系统状态', 'sys_common_status', '0', 'admin', '2022-08-05 01:11:13', '', NULL, '登录状态列表'); + +-- ---------------------------- +-- Table structure for sys_job +-- ---------------------------- +DROP TABLE IF EXISTS `sys_job`; +CREATE TABLE `sys_job` ( + `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名', + `invoke_target` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', + `cron_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'cron执行表达式', + `misfire_policy` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + `concurrent` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1暂停)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注信息', + PRIMARY KEY (`job_id`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '定时任务调度表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_job +-- ---------------------------- +INSERT INTO `sys_job` VALUES (1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', '2022-08-05 01:11:22', '', NULL, ''); +INSERT INTO `sys_job` VALUES (2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', '2022-08-05 01:11:22', '', NULL, ''); +INSERT INTO `sys_job` VALUES (3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', '2022-08-05 01:11:22', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_job_log +-- ---------------------------- +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) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务组名', + `invoke_target` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', + `job_message` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志信息', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '执行状态(0正常 1失败)', + `exception_info` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '异常信息', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`job_log_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '定时任务调度日志表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_job_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_logininfor +-- ---------------------------- +DROP TABLE IF EXISTS `sys_logininfor`; +CREATE TABLE `sys_logininfor` ( + `info_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '访问ID', + `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户账号', + `ipaddr` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '浏览器类型', + `os` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作系统', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', + `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息', + `login_time` datetime(0) NULL DEFAULT NULL COMMENT '访问时间', + PRIMARY KEY (`info_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统访问记录' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_logininfor +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID', + `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', + `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '路由地址', + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径', + `query` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由参数', + `is_frame` int(1) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', + `is_cache` int(1) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', + `menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', + `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', + `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识', + `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#' COMMENT '菜单图标', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`menu_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2018 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单权限表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 4, 'system', NULL, '', 1, 0, 'M', '0', '0', '', 'system', 'admin', '2022-08-05 01:10:56', 'admin', '2022-09-15 20:53:32', '系统管理目录'); +INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 1, 'monitor', NULL, '', 1, 0, 'M', '0', '1', '', 'monitor', 'admin', '2022-08-05 01:10:56', 'admin', '2022-09-15 20:52:30', '系统监控目录'); +INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 3, 'tool', NULL, '', 1, 0, 'M', '0', '1', '', 'tool', 'admin', '2022-08-05 01:10:56', 'admin', '2022-08-05 22:04:50', '系统工具目录'); +INSERT INTO `sys_menu` VALUES (5, '控制台', 0, 0, 'dashboard', NULL, NULL, 1, 0, 'M', '1', '0', NULL, 'AppstoreOutlined', 'admin', '2022-08-05 01:10:56', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (6, '个人', 0, 0, 'account', NULL, NULL, 1, 0, 'M', '1', '0', NULL, 'ProfileOutlined', 'admin', '2022-08-05 01:10:56', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (11, '数据分析', 5, 0, 'analysis', NULL, NULL, 1, 0, 'C', '0', '0', '', '#', 'admin', '2022-08-05 01:10:56', 'admin', '2022-10-21 13:29:38', '数据分析'); +INSERT INTO `sys_menu` VALUES (12, '个人中心', 6, 0, 'center', NULL, NULL, 1, 0, 'C', '1', '0', NULL, '#', 'admin', '2022-08-05 01:10:56', '', NULL, '个人中心'); +INSERT INTO `sys_menu` VALUES (13, '设置', 6, 0, 'settings', NULL, NULL, 1, 0, 'C', '1', '0', NULL, '#', 'admin', '2022-08-05 01:10:56', '', NULL, '设置'); +INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'UserOutlined', 'admin', '2022-08-05 01:10:56', '', NULL, '用户管理菜单'); +INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'ProfileOutlined', 'admin', '2022-08-05 01:10:56', '', NULL, '角色管理菜单'); +INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'MenuUnfoldOutlined', 'admin', '2022-08-05 01:10:56', 'admin', '2022-08-11 01:10:05', '菜单管理菜单'); +INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'ApartmentOutlined', 'admin', '2022-08-05 01:10:56', '', NULL, '部门管理菜单'); +INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', '', 1, 0, 'C', '0', '0', 'system:post:list', 'AppstoreAddOutlined', 'admin', '2022-08-05 01:10:56', '', NULL, '岗位管理菜单'); +INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'BookOutlined', 'admin', '2022-08-05 01:10:56', '', NULL, '字典管理菜单'); +INSERT INTO `sys_menu` VALUES (106, '字典数据', 1, 7, 'dictData', 'system/dictData/index', '', 1, 0, 'C', '1', '0', 'system:dict:list', 'FolderAddOutlined', 'admin', '2022-08-05 01:10:56', '', NULL, '字典数据管理菜单'); +INSERT INTO `sys_menu` VALUES (107, '参数设置', 1, 8, 'config', 'system/config/index', '', 1, 0, 'C', '0', '1', 'system:config:list', 'ProfileOutlined', 'admin', '2022-08-05 01:10:56', 'admin', '2022-10-21 13:33:00', '参数设置菜单'); +INSERT INTO `sys_menu` VALUES (108, '通知公告', 1, 9, 'notice', 'system/notice/index', '', 1, 0, 'C', '1', '1', 'system:notice:list', 'NotificationOutlined', 'admin', '2022-08-05 01:10:56', 'admin', '2022-10-21 13:32:48', '通知公告菜单'); +INSERT INTO `sys_menu` VALUES (109, '日志管理', 1, 10, 'log', '', '', 1, 0, 'M', '1', '0', '', 'BlockOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '日志管理菜单'); +INSERT INTO `sys_menu` VALUES (110, '在线用户', 2, 1, 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'LoginOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '在线用户菜单'); +INSERT INTO `sys_menu` VALUES (111, '定时任务', 2, 2, 'job', 'monitor/job/index', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'ClockCircleOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '定时任务菜单'); +INSERT INTO `sys_menu` VALUES (112, '数据监控', 2, 3, 'druid', 'monitor/druid/index', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'ConsoleSqlOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '数据监控菜单'); +INSERT INTO `sys_menu` VALUES (113, '服务监控', 2, 4, 'server', 'monitor/server/index', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'DesktopOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '服务监控菜单'); +INSERT INTO `sys_menu` VALUES (114, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'MonitorOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '缓存监控菜单'); +INSERT INTO `sys_menu` VALUES (115, '缓存列表', 2, 6, 'cacheList', 'monitor/cache/list', '', 1, 0, 'C', '0', '1', 'monitor:cache:list', 'MonitorOutlined', 'admin', '2022-08-05 01:10:57', 'admin', '2022-08-05 21:36:13', '缓存列表菜单'); +INSERT INTO `sys_menu` VALUES (116, '表单构建', 3, 1, 'build', 'tool/builder/index', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'BlockOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '表单构建菜单'); +INSERT INTO `sys_menu` VALUES (117, '代码生成', 3, 2, 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'PrinterOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '代码生成菜单'); +INSERT INTO `sys_menu` VALUES (118, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'ApiOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '系统接口菜单'); +INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'FileOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '操作日志菜单'); +INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'HddOutlined', 'admin', '2022-08-05 01:10:57', '', NULL, '登录日志菜单'); +INSERT INTO `sys_menu` VALUES (1000, '用户查询', 100, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1001, '用户新增', 100, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1002, '用户修改', 100, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1003, '用户删除', 100, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1004, '用户导出', 100, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1005, '用户导入', 100, 6, '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1006, '重置密码', 100, 7, '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1007, '角色查询', 101, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1008, '角色新增', 101, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1009, '角色修改', 101, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1010, '角色删除', 101, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1011, '角色导出', 101, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1012, '菜单查询', 102, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1013, '菜单新增', 102, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1014, '菜单修改', 102, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1015, '菜单删除', 102, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1016, '部门查询', 103, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1017, '部门新增', 103, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1018, '部门修改', 103, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1019, '部门删除', 103, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1020, '岗位查询', 104, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1021, '岗位新增', 104, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1022, '岗位修改', 104, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1023, '岗位删除', 104, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1024, '岗位导出', 104, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1025, '字典查询', 105, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1026, '字典新增', 105, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2022-08-05 01:10:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1027, '字典修改', 105, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1028, '字典删除', 105, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1029, '字典导出', 105, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1030, '参数查询', 106, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1031, '参数新增', 106, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1032, '参数修改', 106, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1033, '参数删除', 106, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1034, '参数导出', 106, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1035, '公告查询', 107, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1036, '公告新增', 107, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1037, '公告修改', 107, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1038, '公告删除', 107, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1039, '操作查询', 500, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1040, '操作删除', 500, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1041, '日志导出', 500, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1042, '登录查询', 501, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1043, '登录删除', 501, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1044, '日志导出', 501, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1045, '在线查询', 109, 1, '#caidan', 'monitor/online/query', 'a:10', 1, 0, 'C', '0', '0', 'monitor:online:query', 'ShrinkOutlined', 'admin', '2022-08-05 01:10:58', 'admin', '2022-08-11 00:58:36', ''); +INSERT INTO `sys_menu` VALUES (1046, '批量强退', 109, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1047, '单条强退', 109, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1048, '任务查询', 110, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1049, '任务新增', 110, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1050, '任务修改', 110, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1051, '任务删除', 110, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1052, '状态修改', 110, 5, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1053, '任务导出', 110, 7, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1054, '生成查询', 115, 1, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1055, '生成修改', 115, 2, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1056, '生成删除', 115, 3, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1057, '导入代码', 115, 2, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1058, '预览代码', 115, 4, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1059, '生成代码', 115, 5, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2022-08-05 01:10:58', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2002, '模型监控', 0, 1, 'modelMonitor', NULL, '', 1, 0, 'M', '0', '0', '', 'RadarChartOutlined', 'admin', '2022-08-10 01:20:05', 'admin', '2022-09-15 20:53:16', ''); +INSERT INTO `sys_menu` VALUES (2003, '主题管理', 2002, 1, 'theme', 'modelMonitor/theme/index', '', 1, 0, 'C', '0', '0', 'modelMonitor:theme:list', 'SlidersOutlined', 'admin', '2022-08-10 17:03:26', 'admin', '2022-08-18 01:21:07', ''); +INSERT INTO `sys_menu` VALUES (2004, '模型管理', 0, 0, 'model', '', '', 1, 0, 'M', '0', '0', '', 'PieChartOutlined', 'admin', '2022-08-11 01:00:06', 'admin', '2022-09-15 20:53:08', ''); +INSERT INTO `sys_menu` VALUES (2005, '数据库扩展', 2002, 2, 'dataextension', 'modelMonitor/dataextension/index', '', 1, 0, 'C', '0', '0', 'modelMonitor:dataextension:list', 'HomeOutlined', 'admin', '2022-08-11 01:22:02', 'admin', '2022-08-18 01:21:11', ''); +INSERT INTO `sys_menu` VALUES (2009, '主题详情', 2002, 5, 'subjectdetails', 'modelMonitor/subjectdetails/index', '', 1, 0, 'C', '1', '0', 'modelMonitor:subjectdetails:list', 'BoxPlotOutlined', 'admin', '2022-08-18 01:34:30', 'admin', '2022-10-11 11:30:53', ''); +INSERT INTO `sys_menu` VALUES (2010, '在线查询', 0, 3, 'OnlinInquiry', NULL, '', 1, 0, 'M', '0', '0', '', 'ZoomInOutlined', 'admin', '2022-08-19 00:56:42', 'admin', '2022-09-15 20:53:22', ''); +INSERT INTO `sys_menu` VALUES (2014, '主题databasir', 2002, 4, 'databasir', 'modelMonitor/databasir/index', '', 1, 1, 'C', '1', '0', 'modelMonitor:databasir:list', 'SlackSquareOutlined', 'admin', '2022-09-13 18:14:37', 'admin', '2022-10-11 11:31:02', ''); +INSERT INTO `sys_menu` VALUES (2015, '模型设计', 2004, 0, 'modelChildren', 'pdManer/pages/getstore/index', '', 1, 0, 'C', '0', '0', 'pdManer:pages:getstore:index', '#', 'admin', '2022-09-13 18:52:01', 'admin', '2022-09-14 23:59:08', ''); +INSERT INTO `sys_menu` VALUES (2016, '数据库查询', 2010, 0, 'OnlinInquiryChildren', 'OnlinInquiry/src/entryPointFactory', '', 1, 0, 'C', '0', '0', 'OnlinInquiry:src:entryPointFactory', '#', 'admin', '2022-09-13 18:52:55', 'admin', '2022-09-26 09:12:41', ''); +INSERT INTO `sys_menu` VALUES (2017, '告警管理', 2002, 3, 'alarmmanage', 'modelMonitor/alarmmanage/index', NULL, 1, 0, 'C', '0', '0', 'modelMonitor:alarmmanage:list', 'AreaChartOutlined', 'admin', '2022-10-11 11:29:48', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_notice +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice`; +CREATE TABLE `sys_notice` ( + `notice_id` int(4) NOT NULL AUTO_INCREMENT COMMENT '公告ID', + `notice_title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告标题', + `notice_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告类型(1通知 2公告)', + `notice_content` longblob NULL COMMENT '公告内容', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '公告状态(0正常 1关闭)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`notice_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知公告表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_notice +-- ---------------------------- +INSERT INTO `sys_notice` VALUES (1, '温馨提醒:2018-07-01 新版本发布啦', '2', 0xE696B0E78988E69CACE58685E5AEB9, '0', 'admin', '2022-08-05 01:11:26', '', NULL, '管理员'); +INSERT INTO `sys_notice` VALUES (2, '维护通知:2018-07-01 系统凌晨维护', '1', 0xE7BBB4E68AA4E58685E5AEB9, '0', 'admin', '2022-08-05 01:11:26', '', NULL, '管理员'); + +-- ---------------------------- +-- Table structure for sys_oper_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_oper_log`; +CREATE TABLE `sys_oper_log` ( + `oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '模块标题', + `business_type` int(2) NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方式', + `operator_type` int(1) NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作地点', + `oper_param` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求参数', + `json_result` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '返回参数', + `status` int(1) NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', + `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误消息', + `oper_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间', + PRIMARY KEY (`oper_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统操作日志记录' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_oper_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_post`; +CREATE TABLE `sys_post` ( + `post_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '岗位ID', + `post_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位编码', + `post_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称', + `post_sort` int(4) NOT NULL COMMENT '显示顺序', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`post_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '岗位信息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_post +-- ---------------------------- +INSERT INTO `sys_post` VALUES (1, 'ceo', '董事长', 1, '0', 'admin', '2022-08-05 01:10:52', '', NULL, ''); +INSERT INTO `sys_post` VALUES (2, 'se', '项目经理', 2, '0', 'admin', '2022-08-05 01:10:52', '', NULL, ''); +INSERT INTO `sys_post` VALUES (3, 'hr', '人力资源', 3, '0', 'admin', '2022-08-05 01:10:52', '', NULL, ''); +INSERT INTO `sys_post` VALUES (4, 'user', '普通员工', 4, '0', 'admin', '2022-08-05 01:10:52', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色权限字符串', + `role_sort` int(4) NOT NULL COMMENT '显示顺序', + `data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`role_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色信息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2022-08-05 01:10:54', '', NULL, '超级管理员'); +INSERT INTO `sys_role` VALUES (2, '普通角色', 'common', 2, '2', 0, 0, '0', '0', 'admin', '2022-08-05 01:10:54', 'admin', '2022-10-18 14:16:12', '普通角色'); + +-- ---------------------------- +-- Table structure for sys_role_dept +-- ---------------------------- +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`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和部门关联表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_role_dept +-- ---------------------------- +INSERT INTO `sys_role_dept` VALUES (2, 100); +INSERT INTO `sys_role_dept` VALUES (2, 101); +INSERT INTO `sys_role_dept` VALUES (2, 105); + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +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`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_role_menu +-- ---------------------------- +INSERT INTO `sys_role_menu` VALUES (2, 11); +INSERT INTO `sys_role_menu` VALUES (2, 12); +INSERT INTO `sys_role_menu` VALUES (2, 13); +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, 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, 2002); +INSERT INTO `sys_role_menu` VALUES (2, 2003); +INSERT INTO `sys_role_menu` VALUES (2, 2004); +INSERT INTO `sys_role_menu` VALUES (2, 2005); +INSERT INTO `sys_role_menu` VALUES (2, 2009); +INSERT INTO `sys_role_menu` VALUES (2, 2014); +INSERT INTO `sys_role_menu` VALUES (2, 2015); + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID', + `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称', + `user_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户邮箱', + `phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像地址', + `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 102 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, 100, 'admin', 'admin', '00', '15888888888@163.com', '15888888888', '1', '/profile/avatar/blob', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '192.168.113.251', '2022-10-21 13:31:45', 'admin', '2022-08-05 01:10:49', '', '2022-10-21 13:31:45', '管理员'); +INSERT INTO `sys_user` VALUES (2, 105, 'zs', '张三', '00', 'zs@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '192.168.102.64', '2022-10-20 16:21:16', 'admin', '2022-08-05 01:10:49', '', '2022-10-20 16:21:15', '测试员'); + +-- ---------------------------- +-- Table structure for sys_user_post +-- ---------------------------- +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`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_user_post +-- ---------------------------- +INSERT INTO `sys_user_post` VALUES (1, 1); +INSERT INTO `sys_user_post` VALUES (2, 2); + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID', + `project_ids` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '项目ID,多个逗号分隔,例:1,2,3', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of sys_user_role +-- ---------------------------- +INSERT INTO `sys_user_role` VALUES (2, 2, ''); +INSERT INTO `sys_user_role` VALUES (101, NULL, ''); + +SET FOREIGN_KEY_CHECKS = 1; 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