Browse Source

JeecgBoot 3.3.0 版本发布,基于代码生成器的企业级低代码平台

pull/3903/head
zhangdaiscott 2 years ago
parent
commit
0cbdc092d1
  1. 41
      README.md
  2. 2
      ant-design-vue-jeecg/README.md
  3. 7
      ant-design-vue-jeecg/package.json
  4. 4
      ant-design-vue-jeecg/src/api/api.js
  5. 36
      ant-design-vue-jeecg/src/api/manage.js
  6. 17
      ant-design-vue-jeecg/src/components/jeecg/JEditableTable.vue
  7. 3
      ant-design-vue-jeecg/src/components/jeecg/JEditor.vue
  8. 3
      ant-design-vue-jeecg/src/components/jeecg/JFormContainer.vue
  9. 21
      ant-design-vue-jeecg/src/components/jeecg/JModal/JModal.vue
  10. 152
      ant-design-vue-jeecg/src/components/jeecg/JModal/ModalDragMixins.js
  11. 14
      ant-design-vue-jeecg/src/components/jeecg/JSuperQuery.vue
  12. 6
      ant-design-vue-jeecg/src/components/jeecg/JVxeTable/components/JVxeSubPopover.vue
  13. 13
      ant-design-vue-jeecg/src/components/jeecg/JVxeTable/components/JVxeTable.js
  14. 4
      ant-design-vue-jeecg/src/components/jeecg/JVxeTable/components/cells/JVxeUploadCell.vue
  15. 8
      ant-design-vue-jeecg/src/components/jeecg/JVxeTable/mixins/vxe.web.socket.mixins.js
  16. 16
      ant-design-vue-jeecg/src/components/layouts/IframePageView.vue
  17. 21
      ant-design-vue-jeecg/src/components/layouts/TabLayout.vue
  18. 31
      ant-design-vue-jeecg/src/components/tools/HeaderNotice.vue
  19. 7
      ant-design-vue-jeecg/src/components/tools/ShowAnnouncement.vue
  20. 4
      ant-design-vue-jeecg/src/mixins/WebsocketMixin.js
  21. 44
      ant-design-vue-jeecg/src/utils/encryption/signMd5Utils.js
  22. 5
      ant-design-vue-jeecg/src/utils/request.js
  23. 14
      ant-design-vue-jeecg/src/utils/rules.js
  24. 62
      ant-design-vue-jeecg/src/utils/util.js
  25. 1
      ant-design-vue-jeecg/src/views/jeecg/SelectDemo.vue
  26. 98
      ant-design-vue-jeecg/src/views/modules/message/SysMessageTemplateList.vue
  27. 20
      ant-design-vue-jeecg/src/views/modules/message/modules/SysMessageTemplateModal.vue
  28. 13
      ant-design-vue-jeecg/src/views/modules/message/modules/SysMessageTestModal.vue
  29. 19
      ant-design-vue-jeecg/src/views/system/DepartList.vue
  30. 28
      ant-design-vue-jeecg/src/views/system/DepartListSync.vue
  31. 9
      ant-design-vue-jeecg/src/views/system/RoleUserList.vue
  32. 4
      ant-design-vue-jeecg/src/views/system/TenantList.vue
  33. 10
      ant-design-vue-jeecg/src/views/system/UserList.vue
  34. 3
      ant-design-vue-jeecg/src/views/system/modules/DepartModal.vue
  35. 8
      ant-design-vue-jeecg/src/views/system/modules/DeptUserInfo.vue
  36. 2
      ant-design-vue-jeecg/src/views/system/modules/DictItemModal.vue
  37. 11
      ant-design-vue-jeecg/src/views/system/modules/SysAnnouncementModal.vue
  38. 13
      ant-design-vue-jeecg/src/views/system/modules/SysDataSourceModal.vue
  39. 5
      ant-design-vue-jeecg/src/views/system/modules/SysFillRuleModal.vue
  40. 5
      ant-design-vue-jeecg/src/views/system/modules/TenantForm.vue
  41. 74
      ant-design-vue-jeecg/src/views/system/modules/UserModal.vue
  42. 9
      ant-design-vue-jeecg/src/views/user/LoginAccount.vue
  43. 2
      jeecg-boot/README.md
  44. 1487
      jeecg-boot/db/jeecgboot-mysql-5.7.sql
  45. 12244
      jeecg-boot/db/jeecgboot-oracle11g.sql
  46. 26407
      jeecg-boot/db/jeecgboot-sqlserver2019.sql
  47. 93
      jeecg-boot/db/tables_nacos.sql
  48. 16
      jeecg-boot/db/增量SQL/3.1.0升级到3.2.0增量脚本sql
  49. 22
      jeecg-boot/db/增量SQL/3.2升级到3.3增量脚本sql
  50. 2
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-cloud-api/pom.xml
  51. 13
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java
  52. 9
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/fallback/SysBaseAPIFallback.java
  53. 4
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-cloud-api/src/main/java/org/jeecg/config/FeignConfig.java
  54. 2
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-local-api/pom.xml
  55. 13
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-local-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java
  56. 2
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/pom.xml
  57. 2
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/pom.xml
  58. 15
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/dto/message/MessageDTO.java
  59. 4
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java
  60. 20
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/DynamicTable.java
  61. 234
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/CommonConstant.java
  62. 10
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DataBaseConstant.java
  63. 16
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DynamicTableConstant.java
  64. 18
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ProvinceCityArea.java
  65. 30
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java
  66. 9
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java
  67. 2
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java
  68. 68
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/MessageTypeEnum.java
  69. 20
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveDecode.java
  70. 20
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveEncode.java
  71. 21
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveField.java
  72. 81
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/aspect/SensitiveDataAspect.java
  73. 55
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/enums/SensitiveEnum.java
  74. 362
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/util/SensitiveInfoUtil.java
  75. 3
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
  76. 19
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/annotation/EnumDict.java
  77. 31
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java
  78. 79
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java
  79. 2
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
  80. 111
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java
  81. 11
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
  82. 5
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java
  83. 2
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java
  84. 108
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java
  85. 14
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java
  86. 24
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java
  87. 2
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/security/AbstractQueryBlackListHandler.java
  88. 40
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeecgBaseConfig.java
  89. 9
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java
  90. 15
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebSocketConfig.java
  91. 35
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/filter/RequestBodyReserveFilter.java
  92. 52
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/filter/WebsocketFilter.java
  93. 29
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java
  94. 62
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/ThreadLocalDataHelper.java
  95. 55
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/aspect/DynamicTableAspect.java
  96. 55
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.java
  97. 30
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
  98. 6
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java
  99. 14
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java
  100. 20
      jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java
  101. Some files were not shown because too many files have changed in this diff Show More

41
README.md

@ -7,19 +7,19 @@
JEECG BOOT 低代码开发平台(前后端分离版本)
===============
当前最新版本: 3.2.0(发布日期:2022-04-25)
当前最新版本: 3.3.0(发布日期:2022-07-25)
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://jeecg.blog.csdn.net)
[![](https://img.shields.io/badge/version-3.2.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/Blog-官方博客-blue.svg)](https://my.oschina.net/jeecg)
[![](https://img.shields.io/badge/version-3.3.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
项目介绍
项目介绍
-----------------------------------
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
@ -34,12 +34,21 @@ JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计(松耦合)、并支持任务节点灵活配置,既保证了公司流程的保密性,又减少了开发人员的工作量。
项目源码
-----------------------------------
| 仓库 |前端源码Vue3版 | 前端源码Vue2版 | 后端源码 |
|-|-|-|-|
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [ant-design-vue-jeecg](https://github.com/jeecgboot/jeecg-boot/tree/master/ant-design-vue-jeecg) | [jeecg-boot](https://github.com/jeecgboot/jeecg-boot) |
| 码云 | [jeecgboot-vue3](https://gitee.com/jeecg/jeecgboot-vue3) | [ant-design-vue-jeecg](https://gitee.com/jeecg/jeecg-boot/tree/master/ant-design-vue-jeecg) | [jeecg-boot](https://gitee.com/jeecg/jeecg-boot) |
##### 项目说明
| 项目名 | 说明 | 传送门 |
|--------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| `jeecg-boot` | JAVA后台(支持微服务) | [Github](https://github.com/jeecgboot/jeecg-boot) &nbsp;&nbsp; [Gitee](https://gitee.com/jeecg/jeecg-boot) |
| `ant-design-vue-jeecg` |Vue2版前端代码(默认与主项目一起) | |
| `jeecgboot-vue3` | Vue3版前端代码 | [Github](https://github.com/jeecgboot/jeecgboot-vue3) &nbsp;&nbsp; [Gitee](https://gitee.com/jeecg/jeecgboot-vue3) |
| 项目名 | 说明 |
|--------------------|------------------------|
| `jeecg-boot` | SpringBoot后台源码(支持微服务) |
| `ant-design-vue-jeecg` |Vue2版前端源码(与主项目一起) |
| `jeecgboot-vue3` | Vue3版前端源码 |
适用项目
@ -62,10 +71,10 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
- 微服务开发: [单体切换为微服务](http://doc.jeecg.com/2704725)
- QQ交流群 : ⑥730954414、VUE3群683903138、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
> ` 提醒:【QQ群是自助服务群,建议给帮助您解决问题的同学发送指定红包,表示感谢!】 `
为什么选择JEECG-BOOT?
-----------------------------------
* 1.采用最新主流前后分离框架(Springboot+Mybatis+antd),容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发;
@ -340,16 +349,16 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
│ ├─Online在线表单 - 功能已开放
│ ├─Online代码生成器 - 功能已开放
│ ├─Online在线报表 - 功能已开放
│ ├─Online在线图表(商业功能)
│ ├─Online图表模板配置(商业功能)
│ ├─Online布局设计(商业功能)
│ ├─Online在线图表(未开源)
│ ├─Online图表模板配置(未开源)
│ ├─Online布局设计(未开源)
│ ├─多数据源管理 - 功能已开放
├─积木报表设计器(低代码)
│ ├─打印设计器
│ ├─数据报表设计
│ ├─图形报表设计(支持echart)
│ ├─大屏设计器(商业功能)
│─流程模块功能 (商业功能)
│ ├─大屏设计器(未开源)
│─流程模块功能 (未开源)
│ ├─流程设计器
│ ├─表单设计器
├─大屏设计器
@ -364,7 +373,7 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
│ └─我的抄送
│ └─流程委派、抄送、跳转
│ └─。。。
│─OA办公组件 (商业功能)
│─OA办公组件 (未开源)
│ ├─更多功能
│ └─。。。
└─其他模块

2
ant-design-vue-jeecg/README.md

@ -1,7 +1,7 @@
Ant Design Jeecg Vue
====
当前最新版本: 3.1.0(发布日期:20220301
当前最新版本: 3.3.0(发布日期:20220725
Overview
----

7
ant-design-vue-jeecg/package.json

@ -10,8 +10,8 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"ant-design-vue": "^1.7.2",
"@jeecg/antd-online-mini": "3.1.0-beta",
"ant-design-vue": "^1.7.2",
"@antv/data-set": "^0.11.4",
"viser-vue": "^2.4.8",
"axios": "^0.18.0",
@ -29,7 +29,7 @@
"vue-ls": "^3.2.0",
"vue-router": "^3.0.1",
"vuex": "^3.1.0",
"vue-print-nb-jeecg": "^1.0.9",
"vue-print-nb-jeecg": "^1.0.11",
"clipboard": "^2.0.4",
"vue-photo-preview": "^1.1.3",
"vue-splitpane": "^1.0.4",
@ -45,7 +45,8 @@
"vxe-table": "2.9.13",
"vxe-table-plugin-antd": "1.8.10",
"cron-parser": "^2.10.0",
"qiankun": "^2.5.1"
"qiankun": "^2.5.1",
"xss": "^1.0.13"
},
"devDependencies": {
"@babel/polyfill": "^7.2.5",

4
ant-design-vue-jeecg/src/api/api.js

@ -69,7 +69,6 @@ export const ajaxGetDictItems = (code, params)=>getAction(`/sys/dict/getDictItem
function getDictItemsFromCache(dictCode) {
if (Vue.ls.get(UI_CACHE_DB_DICT_DATA) && Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode]) {
let dictItems = Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode];
//console.log("-----------getDictItemsFromCache----------dictCode="+dictCode+"---- dictItems=",dictItems)
return dictItems;
}
}
@ -91,6 +90,7 @@ const loadCategoryData = (params)=>getAction("/sys/category/loadAllData",params)
const checkRuleByCode = (params) => getAction('/sys/checkRule/checkByCode', params)
//加载我的通告信息
const getUserNoticeInfo= (params)=>getAction("/sys/sysAnnouncementSend/getMyAnnouncementSend",params);
//查询图表数据
const getTransitURL = url => `/sys/common/transitRESTful?url=${encodeURIComponent(url)}`
// 中转HTTP请求
export const transitRESTful = {
@ -101,8 +101,6 @@ export const transitRESTful = {
}
export {
// imgView,
// doMian,
addRole,
editRole,
checkRoleCode,

36
ant-design-vue-jeecg/src/api/manage.js

@ -16,7 +16,9 @@ export default api
export function postAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
// update-begin--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getTimestamp()};
// update-end--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
return axios({
url: url,
@ -30,7 +32,9 @@ export function postAction(url,parameter) {
export function httpAction(url,parameter,method) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
// update-begin--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getTimestamp()};
// update-end--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
return axios({
url: url,
@ -53,7 +57,9 @@ export function putAction(url,parameter) {
export function getAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
// update-begin--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getTimestamp()};
// update-end--author:taoyan---date:20220421--for: VUEN-410【签名改造】 X-TIMESTAMP牵扯
return axios({
url: url,
@ -120,13 +126,23 @@ export function saveService(parameter) {
* @param parameter
* @returns {*}
*/
export function downFile(url,parameter){
return axios({
url: url,
params: parameter,
method:'get' ,
responseType: 'blob'
})
export function downFile(url,parameter, method='get'){
if(method=='get'){
return axios({
url: url,
params: parameter,
method: method ,
responseType: 'blob'
})
}else{
return axios({
url: url,
method: method,
data: parameter,
responseType: 'blob'
})
}
}
/**

17
ant-design-vue-jeecg/src/components/jeecg/JEditableTable.vue

@ -387,7 +387,7 @@
<a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownloadFile(id)">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="col.allowRemove!==false" @click="handleClickDelFile(id)">
<a-menu-item v-if="col.allowRemove!==false" @click="handleClickDelFile(id, row, col)">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
</a-menu>
@ -475,7 +475,7 @@
<a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownFileByUrl(id)">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item @click="handleClickDelFile(id)">
<a-menu-item @click="handleClickDelFile(id, row, col)">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(id,col,col)">
@ -532,7 +532,7 @@
<a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownFileByUrl(id)">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item @click="handleClickDelFile(id)">
<a-menu-item @click="handleClickDelFile(id, row, col)">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(id,'img',col)">
@ -1865,6 +1865,8 @@
})
// 强制更新formValues
this.forceUpdateFormValues()
// 【issues/3828】重新计算统计列
this.recalcAllStatisticsColumns()
},
/**
* 设置单个组件的值
@ -2590,8 +2592,9 @@
return id;
},
handleClickDelFile(id) {
handleClickDelFile(id, row, col) {
this.uploadValues[id] = null
this.elemValueChange(col.type, row, col, null);
},
handleClickDownloadFile(id) {
let { path } = this.uploadValues[id] || {}
@ -3074,7 +3077,11 @@
return false
}
return true;
}
},
// 根据id获取dataSource中的一行数据
getOriginData(id){
return this.dataSource.filter(item=>item.id == id);
},
},
beforeDestroy() {

3
ant-design-vue-jeecg/src/components/jeecg/JEditor.vue

@ -68,6 +68,9 @@
branding: false,
menubar: false,
toolbar_drawer: false,
//update-begin-author:taoyan date:2022-5-6 for: issues/I4BCC3 富文本编辑器在服务器图片上传是相对路径
convert_urls: false,
//update-end-author:taoyan date:2022-5-6 for: issues/I4BCC3 富文本编辑器在服务器图片上传是相对路径
images_upload_handler: (blobInfo, success) => {
let formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename());

3
ant-design-vue-jeecg/src/components/jeecg/JFormContainer.vue

@ -49,7 +49,8 @@
.jeecg-form-container-disabled .ant-upload-select{display:none}
.jeecg-form-container-disabled .ant-upload-list{cursor:grabbing}
.jeecg-form-container-disabled fieldset[disabled] .ant-upload-list{
.jeecg-form-container-disabled fieldset[disabled] .ant-upload-list,
.jeecg-form-container-disabled fieldset[disabled] iframe{
-ms-pointer-events: auto !important;
pointer-events: auto !important;
}

21
ant-design-vue-jeecg/src/components/jeecg/JModal/JModal.vue

@ -8,7 +8,7 @@
v-on="$listeners"
@ok="handleOk"
@cancel="handleCancel"
destroyOnClose
:destroyOnClose="destroyOnClose"
>
<slot></slot>
@ -49,13 +49,17 @@
import { getClass, getStyle } from '@/utils/props-util'
import { triggerWindowResizeEvent } from '@/utils/util'
import ModalDragMixins from './ModalDragMixins'
export default {
name: 'JModal',
mixins: [ModalDragMixins],
props: {
title: String,
// 可使用 .sync 修饰符
visible: Boolean,
// 是否开启拖拽
draggable: Boolean,
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
fullscreen: {
type: Boolean,
@ -71,6 +75,11 @@ export default {
type: Boolean,
default: true
},
// 关闭时销毁弹窗内容
destroyOnClose: {
type: Boolean,
default: true
},
},
data() {
return {
@ -162,6 +171,16 @@ export default {
toggleFullscreen() {
this.innerFullscreen = !this.innerFullscreen
triggerWindowResizeEvent()
// 全屏的时候禁止拖动
if (this.innerFullscreen) {
// 还原弹窗的位置为0,0
this.setModalPosition(0, 0, false)
this.dragSettings.headerEl.style.cursor = null
} else {
// 取消全屏的时候,将弹窗移动到上次记录的位置
this.resetModalPosition()
this.dragSettings.headerEl.style.cursor = 'move'
}
},
}

152
ant-design-vue-jeecg/src/components/jeecg/JModal/ModalDragMixins.js

@ -0,0 +1,152 @@
import {getRefPromise} from '@/utils/util'
/** JModal 的拖拽混入 */
export default {
data() {
return {
// 拖动配置
dragSettings: {
// 上次拖动top记录
top: null,
// 上次拖动left记录
left: null,
wrapEl: null,
dragEl: null,
headerEl: null,
},
}
},
watch: {
visible() {
if (!this.visible || !this.draggable) {
return
}
this.handleDrag()
},
draggable() {
if (!this.visible || !this.draggable) {
return
}
this.handleDrag()
},
},
methods: {
async handleDrag() {
let modalRef = await getRefPromise(this, 'modal')
const dragWraps = modalRef.$el.querySelectorAll('.ant-modal-wrap')
let wrapEl = dragWraps[0]
if (!wrapEl) return
this.dragSettings.wrapEl = wrapEl
this.dragSettings.dragEl = wrapEl.querySelector('.ant-modal')
this.dragSettings.headerEl = wrapEl.querySelector('.ant-modal-header')
const display = getStyle(wrapEl, 'display')
const draggable = wrapEl.getAttribute('data-drag')
if (display !== 'none') {
// 拖拽位置
if (draggable === null || this.destroyOnClose) {
this.enableDrag(wrapEl)
}
}
},
/** 启用拖拽 */
enableDrag() {
let {wrapEl, dragEl, headerEl} = this.dragSettings
if (!wrapEl) return
wrapEl.setAttribute('data-drag', this.draggable)
if (!headerEl || !dragEl || !this.draggable) return
// 还原上一次移动的位置
this.resetModalPosition()
headerEl.style.cursor = 'move'
headerEl.onmousedown = (e) => {
if (!e) return
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX
const disY = e.clientY
const screenWidth = document.body.clientWidth // body当前宽度
const screenHeight = document.documentElement.clientHeight // 可见区域高度(应为body高度,可某些环境下无法获取)
const dragElWidth = dragEl.offsetWidth // 对话框宽度
const dragElHeight = dragEl.offsetHeight // 对话框高度
const minDragElLeft = dragEl.offsetLeft
const maxDragElLeft = screenWidth - dragEl.offsetLeft - dragElWidth
const minDragElTop = dragEl.offsetTop
const maxDragElTop = screenHeight - dragEl.offsetTop - dragElHeight
// 获取到的值带px 正则匹配替换
const domLeft = getStyle(dragEl, 'left')
const domTop = getStyle(dragEl, 'top')
let styL = +domLeft
let styT = +domTop
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (domLeft.includes('%')) {
styL = +document.body.clientWidth * (+domLeft.replace(/%/g, '') / 100)
styT = +document.body.clientHeight * (+domTop.replace(/%/g, '') / 100)
} else {
styL = +domLeft.replace(/px/g, '')
styT = +domTop.replace(/px/g, '')
}
document.onmousemove = (e) => {
// 全屏时不触发移动方法
if (this.innerFullscreen) {
return
}
// 通过事件委托,计算移动的距离
let left = e.clientX - disX
let top = e.clientY - disY
// 边界处理
if (-left > minDragElLeft) {
left = -minDragElLeft
} else if (left > maxDragElLeft) {
left = maxDragElLeft
}
if (-top > minDragElTop) {
top = -minDragElTop
} else if (top > maxDragElTop) {
top = maxDragElTop
}
this.setModalPosition(top + styT, left + styL)
}
document.onmouseup = () => {
document.onmousemove = null
document.onmouseup = null
}
}
},
/**
* 移动弹窗位置
* @param top 顶部位置
* @param left 左侧位置
* @param remember 是否记录位置默认 true
*/
setModalPosition(top, left, remember = true) {
// 记录移动位置
if (remember) {
this.dragSettings.top = top
this.dragSettings.left = left
}
// 移动当前元素
this.dragSettings.dragEl.style.cssText += `;left:${left}px;top:${top}px;`
},
/**
* 将弹窗移动到上次记录的位置
*/
resetModalPosition() {
this.setModalPosition(this.dragSettings.top, this.dragSettings.left, false)
},
},
}
function getStyle(dom, attr) {
return getComputedStyle(dom)[attr]
}

14
ant-design-vue-jeecg/src/components/jeecg/JSuperQuery.vue

@ -29,6 +29,7 @@
@cancel="handleCancel"
:mask="false"
:fullscreen="izMobile"
draggable
class="j-super-query-modal"
style="top:5%;max-height: 95%;"
>
@ -163,8 +164,10 @@
<a-time-picker v-else-if="item.type==='time'" :value="item.val ? moment(item.val,'HH:mm:ss') : null" format="HH:mm:ss" style="width: 100%" @change="(time,value)=>item.val=value"/>
<a-input-number v-else-if=" item.type=='int'||item.type=='number' " style="width: 100%" placeholder="请输入数值" v-model="item.val"/>
<a-select v-else-if="item.type=='switch'" placeholder="请选择" v-model="item.val">
<a-select-option value="Y"></a-select-option>
<a-select-option value="N"></a-select-option>
<!-- update-begin-author:taoyan for: VUEN-242online表单 高级查询开关组件设置扩展参数为[0,1] 高级查询选择后查询仍然是Y/N -->
<a-select-option :value="item.extendOption[0]"></a-select-option>
<a-select-option :value="item.extendOption[1]"></a-select-option>
<!-- update-end-author:taoyan for: VUEN-242online表单 高级查询开关组件设置扩展参数为[0,1] 高级查询选择后查询仍然是Y/N -->
</a-select>
<a-input v-else v-model="item.val" placeholder="请输入值"/>
</a-col>
@ -426,10 +429,17 @@
item['dictCode'] = dictCode
item['dictTable'] = dictTable
item['dictText'] = dictText
//update-begin-author:taoyan for: VUEN-242【online表单 高级查询】开关组件设置扩展参数为[0,1] 时,高级查询选择后查询仍然是Y/N
item['extendOption'] = node.dataRef.extendOption || ['Y', 'N']
//update-begin-author:taoyan for: VUEN-242【online表单 高级查询】开关组件设置扩展参数为[0,1] 时,高级查询选择后查询仍然是Y/N
item['customReturnField'] = customReturnField
if (popup) {
item['popup'] = popup
}
// 格式化字符串,一般用于高级查询的日期格式处理
if (node.dataRef.formatStr) {
item['formatStr'] = node.dataRef.formatStr
}
this.$set(item, 'val', undefined)
},
handleOpen() {

6
ant-design-vue-jeecg/src/components/jeecg/JVxeTable/components/JVxeSubPopover.vue

@ -71,6 +71,12 @@
let className = target.className || ''
className = typeof className === 'string' ? className : className.toString()
// 获取 td 父级
let td = getParentNodeByTagName(target, 'td');
// 点击的是拖拽排序列,不做处理
if (td && td.querySelector('.j-vxe-ds-icons')) {
return
}
// 点击的是expand,不做处理
if (className.includes('vxe-table--expand-btn')) {
return

13
ant-design-vue-jeecg/src/components/jeecg/JVxeTable/components/JVxeTable.js

@ -763,6 +763,8 @@ export default {
console.warn(`JVxeTable.setValues必须传递数组`)
return
}
// 是否更新了数据
let updated = false
values.forEach((item, idx) => {
let {rowKey, values: record} = item
let {row} = this.getIfRowById(rowKey)
@ -775,6 +777,7 @@ export default {
let oldValue = row[colKey]
let newValue = record[colKey]
if (newValue !== oldValue) {
updated = true
this.$set(row, colKey, newValue)
// 触发 valueChange 事件
this.trigger('valueChange', {
@ -791,6 +794,14 @@ export default {
}
})
})
// 【issues/3828】数据更新后,重新计算统计列
if (updated && this.statistics.has) {
this.$nextTick(async () => {
let {xTable} = this.$refs.vxe.$refs;
await xTable.updateCache(true);
await xTable.updateData();
});
}
},
/** 获取所有的数据,包括values、deleteIds */
@ -1406,7 +1417,7 @@ const fooPatterns = [
{title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/},
{title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/},
{title: '整数', value: 'z', pattern: /^-?\d+$/},
{title: '金额', value: 'money', pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/},
{title: '金额', value: 'money', pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,5}))$/},
]
/** 旧版handler转为新版Validator */

4
ant-design-vue-jeecg/src/components/jeecg/JVxeTable/components/cells/JVxeUploadCell.vue

@ -133,6 +133,10 @@
value['message'] = file.response.message || '未知错误'
}
this.innerFile = value
// issues/I5FTO6 JVxeTypes.upload 文件上传的时候,触发不了事件
if (value.path) {
this.handleChangeCommon(fileGetValue(value));
}
},
// handleClickPreviewFile(id) {

8
ant-design-vue-jeecg/src/components/jeecg/JVxeTable/mixins/vxe.web.socket.mixins.js

@ -1,5 +1,8 @@
import store from '@/store/'
import { randomUUID } from '@/utils/util'
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types'
// vxe socket
const vs = {
// 页面唯一 id,用于标识同一用户,不同页面的websocket
@ -52,7 +55,10 @@ const vs = {
const domain = window._CONFIG['domianURL'].replace('https://', 'wss://').replace('http://', 'ws://')
const url = `${domain}/vxeSocket/${userId}/${this.pageId}`
this.ws = new WebSocket(url)
//update-begin-author:taoyan date:2022-4-22 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
let token = Vue.ls.get(ACCESS_TOKEN)
this.ws = new WebSocket(url, [token])
//update-end-author:taoyan date:2022-4-22 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
this.ws.onopen = this.on.open.bind(this)
this.ws.onerror = this.on.error.bind(this)
this.ws.onmessage = this.on.message.bind(this)

16
ant-design-vue-jeecg/src/components/layouts/IframePageView.vue

@ -7,12 +7,15 @@
<script>
import Vue from 'vue'
import { ACCESS_TOKEN } from "@/store/mutation-types"
import { TENANT_ID } from "@/store/mutation-types"
import PageLayout from '../page/PageLayout'
import RouteView from './RouteView'
import {mixinDevice} from '@/utils/mixin'
export default {
name: "IframePageContent",
inject:['closeCurrent'],
mixins: [mixinDevice],
data () {
return {
url: "",
@ -47,10 +50,21 @@
} else {
this.url = url
}
//update-begin---author:wangshuai ---date:20220711 for:[VUEN-1638]菜单tenantId需要动态生成------------
let tenantIdStr = '${tenantId}'
let tenantUrl = this.url
if (tenantUrl.indexOf(tenantIdStr) != -1) {
let tenantId = Vue.ls.get(TENANT_ID)
this.url = tenantUrl.replace(tenantIdStr, tenantId)
}
//update-end---author:wangshuai ---date:20220711 for:[VUEN-1638]菜单tenantId需要动态生成--------------
//-----------------------------------------------------------------------------------------
// 是否允许打开外部页面,需要非Mobile模式且打开多页签模式
let allowOpen = !this.isMobile() && this.$store.state.app.multipage
/*update_begin author:wuxianquan date:20190908 for:判断打开方式,新窗口打开时this.$route.meta.internalOrExternal==true */
if(this.$route.meta.internalOrExternal != undefined && this.$route.meta.internalOrExternal==true){
if(allowOpen && this.$route.meta.internalOrExternal === true){
this.closeCurrent();
window.open(this.url);
}

21
ant-design-vue-jeecg/src/components/layouts/TabLayout.vue

@ -70,7 +70,9 @@
/* update_begin author:wuxianquan date:20190828 for: 关闭当前tab页,供子页面调用 ->望菜单能配置外链,直接弹出新页面而不是嵌入iframe #428 */
provide(){
return{
closeCurrent:this.closeCurrent
closeCurrent:this.closeCurrent,
changeTitle: this.changeTitle,
changeTabTitle: this.changeTabTitle,
}
},
/* update_end author:wuxianquan date:20190828 for: 关闭当前tab页,供子页面调用->望菜单能配置外链,直接弹出新页面而不是嵌入iframe #428 */
@ -176,6 +178,10 @@
// update-end-author:sunjianlei date:20191223 for: 修复从单页模式切换回多页模式后首页不居第一位的 BUG
// update-begin-author:sunjianlei date:20200120 for: 动态更改页面标题
/**
* 修改当前页面的窗口标题
* @param title 要修改的新标题
*/
changeTitle(title) {
let projectTitle = "Jeecg-Boot 企业级低代码平台"
// 首页特殊处理
@ -185,6 +191,19 @@
document.title = title + ' · ' + projectTitle
}
},
/**
* 修改tab标签的标题
* @param title 要修改的新标题
* @param fullPath 要修改的路由全路径如果不填就是修改当前路由
*/
changeTabTitle(title, fullPath = '') {
if (title) {
let currentRoute = this.pageList.find((r) => r.fullPath === (fullPath ? fullPath : this.$route.fullPath))
if (currentRoute != null) {
currentRoute.meta = {...currentRoute.meta, title}
}
}
},
// update-end-author:sunjianlei date:20200120 for: 动态更改页面标题
changePage(key) {

31
ant-design-vue-jeecg/src/components/tools/HeaderNotice.vue

@ -81,7 +81,8 @@
import ShowAnnouncement from './ShowAnnouncement'
import store from '@/store/'
import DynamicNotice from './DynamicNotice'
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types'
export default {
name: "HeaderNotice",
@ -107,6 +108,8 @@
stopTimer:false,
websock: null,
lockReconnect:false,
//websocket错误连接次数
wsConnectErrorTime:1,
heartCheck:null,
formData:{},
openPath:''
@ -201,7 +204,10 @@
var userId = store.getters.userInfo.id;
var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://")+"/websocket/"+userId;
//console.log(url);
this.websock = new WebSocket(url);
//update-begin-author:taoyan date:2022-4-22 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
let token = Vue.ls.get(ACCESS_TOKEN)
this.websock = new WebSocket(url, [token]);
//update-end-author:taoyan date:2022-4-22 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
this.websock.onopen = this.websocketOnopen;
this.websock.onerror = this.websocketOnerror;
this.websock.onmessage = this.websocketOnmessage;
@ -213,12 +219,21 @@
//this.heartCheck.reset().start();
},
websocketOnerror: function (e) {
console.log("WebSocket连接发生错误");
console.log("WebSocket连接发生错误,第%s次",this.wsConnectErrorTime);
this.wsConnectErrorTime = this.wsConnectErrorTime + 1;
if(this.wsConnectErrorTime>5){
console.log("WebSocket连接错误超过5次,就不再重新连了!");
this.lockReconnect = true
return;
}
this.reconnect();
},
websocketOnmessage: function (e) {
console.log("-----接收消息-------",e.data);
var data = eval("(" + e.data + ")"); //解析对象
this.voiceBroadcast(data.msgTxt)
if(data.cmd == "topic"){
//系统通知
this.loadData();
@ -243,7 +258,13 @@
console.log("send failed (" + err.code + ")");
}
},
//语音播报系统通知
voiceBroadcast(text){
var url = "http://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&text=" + encodeURI(text); // baidu文字转语音
var voiceContent = new Audio(url);
voiceContent.src = url;
voiceContent.play();
},
openNotification (data) {
var text = data.msgTxt;
const key = `open${Date.now()}`;
@ -275,7 +296,7 @@
console.info("尝试重连...");
that.initWebSocket();
that.lockReconnect = false;
}, 5000);
}, 20000);
},
heartCheckFun(){
var that = this;

7
ant-design-vue-jeecg/src/components/tools/ShowAnnouncement.vue

@ -1,3 +1,4 @@
import xss from "xss"
<template>
<j-modal
:title="title"
@ -24,6 +25,7 @@
<script>
import {getUserList} from '@/api/api'
import xss from 'xss'
export default {
name: "SysAnnouncementModal",
components: {
@ -70,6 +72,11 @@
}
//update-end---author:wangshuai ---date:20220107 for:将其它页面传递过来的用户名改成用户真实姓名
this.visible = true;
//update-begin-author:taoyan date:2022-7-14 for: VUEN-1702 【禁止问题】sql注入漏洞
if(record.msgContent){
record.msgContent = xss(record.msgContent)
}
//update-end-author:taoyan date:2022-7-14 for: VUEN-1702 【禁止问题】sql注入漏洞
this.record = record;
},
handleCancel () {

4
ant-design-vue-jeecg/src/mixins/WebsocketMixin.js

@ -23,7 +23,9 @@ export const WebsocketMixin = {
this.socketUrl = this.socketUrl + '/'
}
var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://") + this.socketUrl + userId + "/" + token;
this.websock = new WebSocket(url);
//update-begin-author:taoyan date:2022-4-22 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
this.websock = new WebSocket(url, [token]);
//update-end-author:taoyan date:2022-4-22 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
this.websock.onopen = this.websocketOnopen;
this.websock.onerror = this.websocketOnerror;
this.websock.onmessage = this.websocketOnmessage;

44
ant-design-vue-jeecg/src/utils/encryption/signMd5Utils.js

@ -104,23 +104,35 @@ export default class signMd5Utils {
return paramStr;
};
static getDateTimeToString() {
const date_ = new Date()
const year = date_.getFullYear()
let month = date_.getMonth() + 1
let day = date_.getDate()
if (month < 10) month = '0' + month
if (day < 10) day = '0' + day
let hours = date_.getHours()
let mins = date_.getMinutes()
let secs = date_.getSeconds()
const msecs = date_.getMilliseconds()
if (hours < 10) hours = '0' + hours
if (mins < 10) mins = '0' + mins
if (secs < 10) secs = '0' + secs
if (msecs < 10) secs = '0' + msecs
return year + '' + month + '' + day + '' + hours + '' + mins + '' + secs
/**
* 接口签名用 生成header中的时间戳
* @returns {number}
*/
static getTimestamp(){
return new Date().getTime()
}
// /**
// * 获取客户端时间(签名参数 X_TIMESTAMP)
// * @returns {string}
// */
// static getDateTimeToString() {
// const date_ = new Date()
// const year = date_.getFullYear()
// let month = date_.getMonth() + 1
// let day = date_.getDate()
// if (month < 10) month = '0' + month
// if (day < 10) day = '0' + day
// let hours = date_.getHours()
// let mins = date_.getMinutes()
// let secs = date_.getSeconds()
// const msecs = date_.getMilliseconds()
// if (hours < 10) hours = '0' + hours
// if (mins < 10) mins = '0' + mins
// if (secs < 10) secs = '0' + secs
// if (msecs < 10) secs = '0' + msecs
// return year + '' + month + '' + day + '' + hours + '' + mins + '' + secs
// }
// true:数值型的,false:非数值型
static myIsNaN(value) {
return typeof value === 'number' && !isNaN(value);

5
ant-design-vue-jeecg/src/utils/request.js

@ -113,6 +113,11 @@ service.interceptors.request.use(config => {
const $route = router.currentRoute
if ($route && $route.name && $route.name.startsWith('low-app') && $route.params.appId) {
config.headers['X-Low-App-ID'] = $route.params.appId
// lowApp自定义筛选条件
if ($route.params.lowAppFilter) {
config.params = {...config.params, ...$route.params.lowAppFilter}
delete $route.params.lowAppFilter
}
}
// update-end--author:sunjianlei---date:20200723---for 如果当前在low-app环境,并且携带了appId,就向Header里传递appId

14
ant-design-vue-jeecg/src/utils/rules.js

@ -1,10 +1,17 @@
const validateMobile = (rule, value, callback) => {
let reg = /^1(3|4|5|7|8)\d{9}$/
if (!reg.test(value)) {
callback('请输入正确手机号')
} else {
//update-beign-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候,触发手机号校验
if(!value && value!=='0'){
callback()
}else{
if (!reg.test(value)) {
callback('请输入正确手机号')
} else {
callback()
}
}
//update-end-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候,触发手机号校验
}
const validateEn = (rule, value, callback) => {
let reg = /^[_a-zA-Z0-9]+$/
@ -24,6 +31,7 @@ export const rules = {
message: '请输入手机号',
trigger: 'blur'
}, { validator: validateMobile, trigger: 'blur' }],
mobile2: [{ validator: validateMobile, trigger: 'blur' }],
userName: [{
required: true, message: '请输入用户名', trigger: 'blur'
}, { validator: validateEn }],

62
ant-design-vue-jeecg/src/utils/util.js

@ -206,7 +206,7 @@ export function randomNumber() {
}
if (arguments.length === 1) {
let [length] = arguments
// 生成指定长度的随机数字,首位一定不是 0
// 生成指定长度的随机数字,首位一定不是 0
let nums = [...Array(length).keys()].map((i) => (i > 0 ? random(0, 9) : random(1, 9)))
return parseInt(nums.join(''))
} else if (arguments.length >= 2) {
@ -627,3 +627,63 @@ export function aspectAroundFunction(obj, funcName, callback) {
})
}
}
/**
* 休眠
* @param ms 毫秒
* @return {Promise<unknown>}
*/
export function sleep(ms) {
return new Promise(function (resolve) {
return setTimeout(resolve, ms);
});
}
/**
* 获取指定的 $refs 对象
* 有时候可能会遇到组件未挂载到页面中的情况导致无法获取 $refs 中的某个对象
* 这个方法可以等待挂载完成之后再返回 $refs 的对象避免报错
*
* 用法示例let modalRef = getRefPromise(this, 'modal')
* @param vm vue实例
* @param name 要获取的ref名称
* @param noComment $el 标签不能是注释
**/
export function getRefPromise(vm, name, noComment = true) {
return new Promise((resolve) => {
(function next() {
let ref = vm.$refs[name]
if (ref && (noComment && ref.$el.tagName)) {
resolve(ref)
} else {
setTimeout(() => {
if (noComment) {
vm.$forceUpdate()
}
next()
}, 10)
}
})()
})
}
/**
* 导出文件xlsx的mime-type
* xls: application/vnd.ms-excel
* @type {string}
*/
export const EXPORT_MIME_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
/**
* 导出excel文件后缀
* @type {string}
*/
export const EXPORT_FILE_SUFFIX = ".xlsx";
/**
* 字符串是否为null或null字符串
* @param str
* @return {boolean}
*/
export function stringIsNull(str) {
return str == null || str === 'null' || str === 'undefined';
}

1
ant-design-vue-jeecg/src/views/jeecg/SelectDemo.vue

@ -538,6 +538,7 @@
superQuery: {
fieldList: [
{ type: 'input', value: 'name', text: '姓名', },
{ type: 'switch', value: 'switch', text: '开关', },
{ type: 'select', value: 'sex', text: '性别', dictCode: 'sex' },
{ type: 'number', value: 'age', text: '年龄', },
{

98
ant-design-vue-jeecg/src/views/modules/message/SysMessageTemplateList.vue

@ -24,7 +24,7 @@
</a-col>
<a-col :md="6" :sm="8">
<a-form-item label="模板类型">
<a-input placeholder="请输入模板类型" v-model="queryParam.templateType"></a-input>
<j-dict-select-tag placeholder="请选择模板类型" v-model="queryParam.templateType" dictCode="msgType"></j-dict-select-tag>
</a-form-item>
</a-col>
</template>
@ -51,7 +51,7 @@
@change="handleImportExcel">
<a-button type="primary" icon="import">导入</a-button>
</a-upload>
<a-dropdown v-if="selectedRowKeys.length > 0">
<!-- <a-dropdown v-if="selectedRowKeys.length > 0">
<a-menu slot="overlay">
<a-menu-item key="1" @click="batchDel">
<a-icon type="delete"/>
@ -61,7 +61,7 @@
<a-button style="margin-left: 8px"> 批量操作
<a-icon type="down"/>
</a-button>
</a-dropdown>
</a-dropdown>-->
</div>
<!-- table区域-begin -->
@ -91,14 +91,21 @@
<span slot="action" slot-scope="text, record">
<a @click="handleEdit(record)">编辑</a>
<a @click="handleMyEdit(record)">编辑</a>
<a-divider type="vertical"/>
<a-dropdown>
<a class="ant-dropdown-link">更多 <a-icon type="down"/></a>
<a-menu slot="overlay">
<a-menu-item>
<a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
<a @click="handleUse(record)">应用</a>
</a-menu-item>
<a-menu-item>
<a @click="handleNotUse(record)">停用</a>
</a-menu-item>
<a-menu-item>
<a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record)">
<a>删除</a>
</a-popconfirm>
</a-menu-item>
@ -125,6 +132,10 @@
import SysMessageTestModal from './modules/SysMessageTestModal'
import {JeecgListMixin} from '@/mixins/JeecgListMixin'
import JEllipsis from "@/components/jeecg/JEllipsis";
import {httpAction} from '@/api/manage'
import { deleteAction } from '@/api/manage'
import JDictSelectTag from '@/components/dict/JDictSelectTag.vue'
export default {
name: "SysMessageTemplateList",
@ -132,7 +143,8 @@
components: {
JEllipsis,
SysMessageTemplateModal,
SysMessageTestModal
SysMessageTestModal,
JDictSelectTag
},
data() {
return {
@ -171,16 +183,22 @@
dataIndex: 'templateType',
customRender: function (text) {
if(text=='1') {
return "短信";
return "文本";
}
if(text=='2') {
return "邮件";
}
if(text=='3') {
return "微信";
return "富文本";
}
if(text=='4') {
return "系统";
}
},
{
title: '是否应用',
align: "center",
dataIndex: 'useStatus',
customRender: function (text) {
if(text=='1') {
return "是";
}else{
return '否'
}
}
},
@ -209,7 +227,59 @@
handleTest(record){
this.$refs.testModal.open(record);
this.$refs.testModal.title = "发送测试";
}
},
//update-begin-author:taoyan date:2022-7-8 for: 修改应用状态
updateUseStatus(record, useStatus){
let formData = {
id: record.id, useStatus: useStatus
}
httpAction("/sys/message/sysMessageTemplate/edit", formData, 'put').then((res) => {
if (res.success) {
this.$message.success(res.message);
} else {
this.$message.warning(res.message);
}
}).finally(() => {
this.loadData()
})
},
handleUse(record){
this.updateUseStatus(record, '1')
},
handleNotUse(record){
this.updateUseStatus(record, '0')
},
handleMyEdit(record){
if(record.useStatus == '1'){
this.$message.warning('此模板已被应用禁止编辑!');
}else{
this.handleEdit(record);
}
},
//update-end-author:taoyan date:2022-7-8 for: 修改应用状态
handleDelete: function (record) {
if(!this.url.delete){
this.$message.error("请设置url.delete属性!")
return
}
if(record.useStatus=='1'){
this.$message.error("该模板已被应用禁止删除!")
return
}
let id = record.id;
var that = this;
deleteAction(that.url.delete, {id: id}).then((res) => {
if (res.success) {
//重新计算分页问题
that.reCalculatePage(1)
that.$message.success(res.message);
that.loadData();
} else {
that.$message.warning(res.message);
}
});
},
}
}

20
ant-design-vue-jeecg/src/views/modules/message/modules/SysMessageTemplateModal.vue

@ -50,6 +50,17 @@
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="24" >
<a-col :span="24" pull="2">
<a-form-item
:labelCol="labelCol"
:wrapperCol="wrapperCol"
label="是否应用"
style="margin-left: -15px">
<j-switch v-decorator="['useStatus', validatorRules.useStatus]" :options="['1', '0']"></j-switch>
</a-form-item>
</a-col>
</a-row>
<a-row class="form-row" :gutter="24">
<a-col :span="24" pull="4">
<a-form-item
@ -85,11 +96,13 @@
import pick from 'lodash.pick'
import { duplicateCheck } from '@/api/api'
import JEditor from '@/components/jeecg/JEditor'
import JSwitch from '@/components/jeecg/JSwitch'
export default {
name: "SysMessageTemplateModal",
components:{
JEditor
JEditor,
JSwitch
},
data() {
return {
@ -111,6 +124,7 @@
templateCode: {rules: [{required: true, message: '请输入模板CODE!' },{validator: this.validateTemplateCode}]},
templateName: {rules: [{required: true, message: '请输入模板标题!'}]},
templateContent: {rules: []},
useStatus:{rules: []},
templateType: {rules: [{required: true, message: '请输入模板类型!'}]},
},
url: {
@ -140,9 +154,9 @@
this.visible = true;
this.$nextTick(() => {
if(this.useEditor){
this.form.setFieldsValue(pick(this.model, 'templateCode', 'templateName', 'templateTestJson', 'templateType'))
this.form.setFieldsValue(pick(this.model, 'useStatus', 'templateCode', 'templateName', 'templateTestJson', 'templateType'))
}else{
this.form.setFieldsValue(pick(this.model, 'templateCode', 'templateContent', 'templateName', 'templateTestJson', 'templateType'))
this.form.setFieldsValue(pick(this.model, 'useStatus', 'templateCode', 'templateContent', 'templateName', 'templateTestJson', 'templateType'))
}
});
},

13
ant-design-vue-jeecg/src/views/modules/message/modules/SysMessageTestModal.vue

@ -34,14 +34,15 @@
label="消息类型">
<j-dict-select-tag
v-model="msgType"
type="radio"
placeholder="请选择消息类型"
dictCode="msgType"/>
dictCode="messageType"/>
</a-form-item>
<a-form-item
:labelCol="labelCol"
:wrapperCol="wrapperCol"
label="消息接收方">
<a-input placeholder="请输入消息接收方" v-model="receiver"/>
<j-select-user-by-dep placeholder="请选择消息接收方" v-model="receiver"></j-select-user-by-dep>
</a-form-item>
</a-form>
</a-spin>
@ -50,9 +51,13 @@
<script>
import {httpAction} from '@/api/manage'
import JSelectUserByDep from '@/components/jeecgbiz/JSelectUserByDep'
export default {
name: "SysMessageTestModal",
components:{
JSelectUserByDep
},
data() {
return {
title: "操作",
@ -74,7 +79,7 @@
templateName: "",
templateContent: "",
receiver: "",
msgType: "",
msgType: "system",
testData: "",
sendParams: {}
}
@ -89,7 +94,7 @@
},
close() {
this.receiver = "";
this.msgType = "";
this.msgType = "system";
this.sendParams = {};
this.visible = false;
},

19
ant-design-vue-jeecg/src/views/system/DepartList.vue

@ -110,10 +110,13 @@
</a-radio-group>
</template>
</a-form-model-item>
<a-form-model-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="部门负责人">
<j-select-multi-user v-model="model.directorUserIds" valueKey="id"></j-select-multi-user>
</a-form-model-item>
<a-form-model-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="排序">
<a-input-number v-model="model.departOrder" />
</a-form-model-item>
<a-form-model-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="手机号">
<a-form-model-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="手机号" prop="mobile">
<a-input placeholder="请输入手机号" v-model="model.mobile" />
</a-form-model-item>
<a-form-model-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="地址">
@ -149,6 +152,7 @@
import {httpAction, deleteAction} from '@/api/manage'
import {JeecgListMixin} from '@/mixins/JeecgListMixin'
import DepartAuthModal from './modules/DepartAuthModal'
import Vue from 'vue'
// 表头
const columns = [
{
@ -236,7 +240,7 @@
departName: [{required: true, message: '请输入机构/部门名称!'}],
orgCode: [{required: true, message: '请输入机构编码!'}],
orgCategory:[{required: true, message: '请输入机构类型!'}],
mobile:[{validator: this.validateMobile}]
mobile: Vue.prototype.rules.mobile2
},
url: {
delete: '/sys/sysDepart/delete',
@ -246,6 +250,7 @@
importExcelUrl: "sys/sysDepart/importExcel",
},
orgCategoryDisabled:false,
oldDirectorUserIds:""
}
},
computed: {
@ -394,7 +399,13 @@
this.model.parentId = record.parentId
this.setValuesToForm(record)
this.$refs.departAuth.show(record.id);
this.oldDirectorUserIds = record.directorUserIds
//update-beign-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候,触发手机号校验
this.$nextTick(()=>{
this.$refs.form.validateField('mobile')
})
//update-end-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候,触发手机号校验
},
// 触发onSelect事件时,为部门树右侧的form表单赋值
setValuesToForm(record) {
@ -431,6 +442,10 @@
return
}
//update-begin---author:wangshuai ---date:20200308 for:[JTC-119]在部门管理菜单下设置部门负责人
this.currSelected.oldDirectorUserIds = this.oldDirectorUserIds
//update-end---author:wangshuai ---date:20200308 for:[JTC-119]在部门管理菜单下设置部门负责人
httpAction(this.url.edit, this.currSelected, 'put').then((res) => {
if (res.success) {
this.$message.success('保存成功!')

28
ant-design-vue-jeecg/src/views/system/DepartListSync.vue

@ -105,6 +105,9 @@
</a-radio-group>
</template>
</a-form-model-item>
<a-form-model-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="部门负责人">
<j-select-multi-user v-model="model.directorUserIds" valueKey="id"></j-select-multi-user>
</a-form-model-item>
<a-form-model-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="排序" prop="departOrder">
<a-input-number v-model="model.departOrder"/>
</a-form-model-item>
@ -146,6 +149,7 @@
import DepartAuthModal from './modules/DepartAuthModal'
import { cloneObject } from '@/utils/util'
import JThirdAppButton from '@comp/jeecgbiz/thirdApp/JThirdAppButton'
import Vue from 'vue'
// 表头
const columns = [
{
@ -239,7 +243,7 @@
departName: [{required: true, message: '请输入机构/部门名称!'}],
orgCode: [{required: true, message: '请输入机构编码!'}],
orgCategory: [{required: true, message: '请输入机构类型!'}],
mobile: [{validator: this.validateMobile}]
mobile: Vue.prototype.rules.mobile2
},
url: {
delete: '/sys/sysDepart/delete',
@ -249,6 +253,7 @@
importExcelUrl: "sys/sysDepart/importExcel",
},
orgCategoryDisabled:false,
oldDirectorUserIds:"" //旧的负责人id
}
},
computed: {
@ -435,7 +440,15 @@
this.model.parentId = record.parentId
this.setValuesToForm(record)
this.$refs.departAuth.show(record.id);
//update-begin---author:wangshuai ---date:20220316 for:[JTC-119]在部门管理菜单下设置部门负责人
this.oldDirectorUserIds = record.directorUserIds
//update-end---author:wangshuai ---date:20220316 for:[JTC-119]在部门管理菜单下设置部门负责人
//update-beign-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候,触发手机号校验
this.$nextTick(()=>{
this.$refs.form.validateField('mobile')
})
//update-end-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候,触发手机号校验
},
// 触发onSelect事件时,为部门树右侧的form表单赋值
setValuesToForm(record) {
@ -478,6 +491,9 @@
let formData = Object.assign(this.currSelected, this.model)
console.log('Received values of form: ', formData)
//update-begin---author:wangshuai ---date:20220316 for:[JTC-119]在部门管理菜单下设置部门负责人
formData.oldDirectorUserIds = this.oldDirectorUserIds
//update-end---author:wangshuai ---date:20220316 for:[JTC-119]在部门管理菜单下设置部门负责人
httpAction(this.url.edit, formData, 'put').then((res) => {
if (res.success) {
this.$message.success('保存成功!')
@ -598,16 +614,6 @@
}
},
// <!---- author:os_chengtgen -- date:20190827 -- for:切换父子勾选模式 =======------>
// 验证手机号
validateMobile(rule,value,callback){
if (!value || new RegExp(/^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/).test(value)){
callback();
}else{
callback("您的手机号码格式不正确!");
}
},
onSyncFinally({isToLocal}) {
// 同步到本地时刷新下数据
if (isToLocal) {

9
ant-design-vue-jeecg/src/views/system/RoleUserList.vue

@ -63,7 +63,6 @@
<a @click="handleOpen(record)">用户</a>
<a-divider type="vertical"/>
<a-dropdown>
<a class="ant-dropdown-link">
更多 <a-icon type="down"/>
@ -125,7 +124,7 @@
<a-menu slot="overlay">
<a-menu-item key="1" @click="batchDel2">
<a-icon type="delete"/>
删除
取消关联
</a-menu-item>
</a-menu>
<a-button style="margin-left: 8px"> 批量操作
@ -161,8 +160,8 @@
</a>
<a-menu slot="overlay">
<a-menu-item>
<a-popconfirm title="确定删除吗?" @confirm="() => handleDelete2(record.id)">
<a>删除</a>
<a-popconfirm title="确定取消关联吗?" @confirm="() => handleDelete2(record.id)">
<a>取消关联</a>
</a-popconfirm>
</a-menu-item>
</a-menu>
@ -453,7 +452,7 @@
var that = this
console.log(this.currentDeptId)
this.$confirm({
title: '确认删除',
title: '确认取消关联',
content: '是否删除选中数据?',
onOk: function() {
deleteAction(that.url.deleteBatch2, { roleId: that.currentRoleId, userIds: ids }).then((res) => {

4
ant-design-vue-jeecg/src/views/system/TenantList.vue

@ -115,7 +115,7 @@
align:"center",
dataIndex: 'id'
},
{
/*{
title:'开始时间',
align:"center",
dataIndex: 'beginDate'
@ -124,7 +124,7 @@
title:'结束时间',
align:"center",
dataIndex: 'endDate'
},
},*/
{
title:'状态',
align:"center",

10
ant-design-vue-jeecg/src/views/system/UserList.vue

@ -66,7 +66,7 @@
<!-- 操作按钮区域 -->
<div class="table-operator" style="border-top: 5px">
<a-button @click="handleAdd" type="primary" icon="plus" >添加用户</a-button>
<a-button type="primary" icon="download" @click="handleExportXls('用户信息')">导出</a-button>
<a-button type="primary" icon="download" @click="handleExportXls('用户信息')">导出</a-button>
<a-upload name="file" :showUploadList="false" :multiple="false" :headers="tokenHeader" :action="importExcelUrl" @change="handleImportExcel">
<a-button type="primary" icon="import">导入</a-button>
</a-upload>
@ -156,10 +156,6 @@
</a-popconfirm>
</a-menu-item>
<a-menu-item>
<a href="javascript:;" @click="handleAgentSettings(record.username)">代理人</a>
</a-menu-item>
</a-menu>
</a-dropdown>
</span>
@ -378,10 +374,6 @@
handleChangePassword(username) {
this.$refs.passwordmodal.show(username);
},
handleAgentSettings(username){
this.$refs.sysUserAgentModal.agentSettings(username);
this.$refs.sysUserAgentModal.title = "用户代理人设置";
},
passwordModalOk() {
//TODO 密码修改完成 不需要刷新页面,可以把datasource中的数据更新一下
},

3
ant-design-vue-jeecg/src/views/system/modules/DepartModal.vue

@ -53,6 +53,9 @@
</a-radio-group>
</template>
</a-form-model-item>
<a-form-model-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="部门负责人">
<j-select-multi-user v-model="model.directorUserIds" valueKey="id"></j-select-multi-user>
</a-form-model-item>
<a-form-model-item
:labelCol="labelCol"
:wrapperCol="wrapperCol"

8
ant-design-vue-jeecg/src/views/system/modules/DeptUserInfo.vue

@ -281,6 +281,10 @@
this.$refs.modalForm.title = "编辑";
this.$refs.modalForm.departDisabled = true;
this.$refs.modalForm.disableSubmit = false;
//update-begin---author:wangshuai ---date:20220315 for:[issues/3472]给新建用户赋予角色的逻辑漏洞------------
//部门中角色信息隐藏掉
this.$refs.modalForm.roleDisabled = true
//update-end---author:wangshuai ---date:20220315 for:[issues/3472]给新建用户赋予角色的逻辑漏洞------------
this.$refs.modalForm.edit(record);
},
handleAdd: function () {
@ -288,6 +292,10 @@
this.$message.error("请选择一个部门!")
} else {
this.$refs.modalForm.departDisabled = true;
//update-begin---author:wangshuai ---date:20220315 for:[issues/3472]给新建用户赋予角色的逻辑漏洞------------
//部门中角色信息隐藏掉
this.$refs.modalForm.roleDisabled = true
//update-end---author:wangshuai ---date:20220315 for:[issues/3472]给新建用户赋予角色的逻辑漏洞------------
//初始化负责部门
this.$refs.modalForm.nextDepartOptions=[{value:this.currentDept.key,label:this.currentDept.title}]
this.$refs.modalForm.title = "新增";

2
ant-design-vue-jeecg/src/views/system/modules/DictItemModal.vue

@ -164,7 +164,7 @@
param.id = this.model.id
}
if(value){
let reg=new RegExp("[`_~!@#$^&*()=|{}'.<>《》/?!¥()—【】‘;:”“。,、?]")
let reg=new RegExp("[`~!@#$^&*()=|{}'.<>《》/?!¥()—【】‘;:”“。,、?]")
if(reg.test(value)){
callback("数据值不能包含特殊字符!")
}else{

11
ant-design-vue-jeecg/src/views/system/modules/SysAnnouncementModal.vue

@ -99,7 +99,7 @@
edit: "/sys/annountCement/edit",
},
userType:false,
userIds:[],
userIds:"",
selectedUser:[],
disabled:false,
msgContent:"",
@ -191,16 +191,19 @@
},
resetUser (){
this.userType = false;
this.userIds = [];
//update-begin---author:wangshuai ---date:20220318 for:[issues/I4X63V]vue有些页面报错,但是在线演示的却没有-----
this.userIds ="";
//update-end---author:wangshuai ---date:20220318 for:[issues/I4X63V]vue有些页面报错,但是在线演示的却没有-----
this.disabled = false;
this.$refs.UserListModal.edit(null,null);
},
chooseMsgType(e) {
if("USER" == e.target.value) {
this.userType = true;
} else {
this.userType = false;
this.userIds = [];
//update-begin---author:wangshuai ---date:20220318 for:[issues/I4X63V]vue有些页面报错,但是在线演示的却没有-----
this.userIds = "";
//update-end---author:wangshuai ---date:20220318 for:[issues/I4X63V]vue有些页面报错,但是在线演示的却没有-----
}
},
startTimeValidate(rule,value,callback){

13
ant-design-vue-jeecg/src/views/system/modules/SysDataSourceModal.vue

@ -79,7 +79,7 @@
<script>
import pick from 'lodash.pick'
import { httpAction, postAction } from '@/api/manage'
import { httpAction, postAction,getAction } from '@/api/manage'
import { validateDuplicateValue } from '@/utils/util'
export default {
@ -129,6 +129,7 @@
url: {
add: '/sys/dataSource/add',
edit: '/sys/dataSource/edit',
queryById: '/sys/dataSource/queryById',
},
dbDriverMap: {
// MySQL 数据库
@ -202,9 +203,17 @@
add() {
this.edit({})
},
edit(record) {
async edit(record) {
this.form.resetFields()
this.model = Object.assign({}, record)
//update-begin-author:liusq---date:20220705--for: 编辑时,查询获取解密后的密码 ---
if(record.id){
let res = await getAction(this.url.queryById, {id:record.id});
if (res.success) {
this.model = Object.assign({}, {...res.result})
}
}
//update-end-author:liusq---date:20220705--for: 编辑时,查询获取解密后的密码 ---
this.visible = true
this.$nextTick(() => {
this.form.setFieldsValue(pick(this.model, 'code', 'name', 'remark', 'dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword'))

5
ant-design-vue-jeecg/src/views/system/modules/SysFillRuleModal.vue

@ -55,6 +55,11 @@
ruleClass: [{ required: true, message: '规则实现类不能为空' }],
ruleParams: [{
validator: (rule, value, callback) => {
//update-begin---author:wangshuai ---date:20220509 for:[VUEN-907]规则参数不是必填,如果为空不检验即可------------
if (!value) {
callback()
}
//update-end---author:wangshuai ---date:20220509 for:[VUEN-907]规则参数不是必填,如果为空不检验即可--------------
try {
let json = JSON.parse(value)

5
ant-design-vue-jeecg/src/views/system/modules/TenantForm.vue

@ -15,7 +15,7 @@
</a-form-model-item>
</a-col>
<a-col :span="24">
<!-- <a-col :span="24">
<a-form-model-item label="开始时间" :labelCol="labelCol" :wrapperCol="wrapperCol">
<j-date placeholder="请选择开始时间" v-model="model.beginDate" :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" style="width: 100%"/>
</a-form-model-item>
@ -24,7 +24,8 @@
<a-form-model-item label="结束时间" :labelCol="labelCol" :wrapperCol="wrapperCol">
<j-date placeholder="请选择结束时间" v-model="model.endDate" :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" style="width: 100%"/>
</a-form-model-item>
</a-col>
</a-col>-->
<a-col :span="24">
<a-form-model-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group name="tenantStatus" v-model="model.status">

74
ant-design-vue-jeecg/src/views/system/modules/UserModal.vue

@ -1,13 +1,13 @@
<template>
<a-drawer
:title="title"
:maskClosable="true"
:width="drawerWidth"
placement="right"
:closable="true"
@close="handleCancel"
:visible="visible"
style="height: 100%;">
:title="title"
:maskClosable="true"
:width="drawerWidth"
placement="right"
:closable="true"
@close="handleCancel"
:visible="visible"
style="height: 100%;">
<template slot="title">
<div style="width: 100%;">
@ -54,10 +54,10 @@
<a-form-model-item label="角色分配" :labelCol="labelCol" :wrapperCol="wrapperCol" v-show="!roleDisabled" >
<j-multi-select-tag
:disabled="disableSubmit"
v-model="model.selectedroles"
:options="rolesOptions"
placeholder="请选择角色">
:disabled="disableSubmit"
v-model="model.selectedroles"
:options="rolesOptions"
placeholder="请选择角色">
</j-multi-select-tag>
</a-form-model-item>
@ -69,10 +69,10 @@
<!--租户分配-->
<a-form-model-item label="租户分配" :labelCol="labelCol" :wrapperCol="wrapperCol" v-show="!departDisabled">
<j-multi-select-tag
:disabled="disableSubmit"
v-model="model.relTenantIds"
:options="tenantsOptions"
placeholder="请选择租户">
:disabled="disableSubmit"
v-model="model.relTenantIds"
:options="tenantsOptions"
placeholder="请选择租户">
</j-multi-select-tag>
</a-form-model-item>
@ -82,12 +82,12 @@
<a-radio :value="2">上级</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="负责部门" :labelCol="labelCol" :wrapperCol="wrapperCol" v-if="departIdShow==true">
<a-form-model-item label="负责部门" :labelCol="labelCol" :wrapperCol="wrapperCol" v-show="departIdShow==true">
<j-multi-select-tag
:disabled="disableSubmit"
v-model="model.departIds"
:options="nextDepartOptions"
placeholder="请选择负责部门">
:disabled="disableSubmit"
v-model="model.departIds"
:options="nextDepartOptions"
placeholder="请选择负责部门">
</j-multi-select-tag>
</a-form-model-item>
@ -97,11 +97,11 @@
<a-form-model-item label="生日" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-date-picker
style="width: 100%"
placeholder="请选择生日"
v-model="model.birthday"
:format="dateFormat"
:getCalendarContainer="node => node.parentNode"/>
style="width: 100%"
placeholder="请选择生日"
v-model="model.birthday"
:format="dateFormat"
:getCalendarContainer="node => node.parentNode"/>
</a-form-model-item>
<a-form-model-item label="性别" :labelCol="labelCol" :wrapperCol="wrapperCol">
@ -162,17 +162,17 @@
dateFormat:"YYYY-MM-DD",
validatorRules:{
username:[{required: true, message: '请输入用户账号!'},
{validator: this.validateUsername,}],
{validator: this.validateUsername,}],
password: [{required: true,pattern:/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~!@#$%^&*()_+`\-={}:";'<>?,./]).{8,}$/,message: '密码由8位数字大小写字母和特殊符号组成!'},
{validator: this.validateToNextPassword,trigger: 'change'}],
{validator: this.validateToNextPassword,trigger: 'change'}],
confirmpassword: [{required: true, message: '请重新输入登录密码!',},
{ validator: this.compareToFirstPassword,}],
{ validator: this.compareToFirstPassword,}],
realname:[{ required: true, message: '请输入用户名称!' }],
phone: [{required: true, message: '请输入手机号!'}, {validator: this.validatePhone}],
email: [{validator: this.validateEmail}],
roles:{},
workNo:[ { required: true, message: '请输入工号' },
{ validator: this.validateWorkNo }],
{ validator: this.validateWorkNo }],
telephone: [{ pattern: /^0\d{2,3}-[1-9]\d{6,7}$/, message: '请输入正确的座机号码' },]
},
departIdShow:false,
@ -441,11 +441,11 @@
};
duplicateCheck(params).then((res) => {
if (res.success) {
callback()
} else {
callback("用户名已存在!")
}
})
callback()
} else {
callback("用户名已存在!")
}
})
},
validateWorkNo(rule, value, callback){
var params = {
@ -476,9 +476,9 @@
},
identityChange(e){
if(e.target.value===1){
this.departIdShow=false;
this.departIdShow=false;
}else{
this.departIdShow=true;
this.departIdShow=true;
}
}
}

9
ant-design-vue-jeecg/src/views/user/LoginAccount.vue

@ -43,8 +43,8 @@
currdatetime: '',
loginType: 0,
model:{
username: '',
password: '',
username: 'admin',
password: '123456',
inputCode: ''
},
validatorRules:{
@ -136,6 +136,11 @@
this.Login(loginParams).then((res) => {
this.$emit('success', res.result)
}).catch((err) => {
//update-begin-author: taoyan date:20220425 for: 登录页面,当输入验证码错误时,验证码图片要刷新一下,而不是保持旧的验证码图片不变 #41
if(err && err.code===412){
this.handleChangeCheckCode();
}
//update-end-author: taoyan date:20220425 for: 登录页面,当输入验证码错误时,验证码图片要刷新一下,而不是保持旧的验证码图片不变 #41
this.$emit('fail', err)
});
}else{

2
jeecg-boot/README.md

@ -1,7 +1,7 @@
Jeecg-Boot 低代码开发平台
===============
当前最新版本: 3.2.0(发布日期:20220425)
当前最新版本: 3.3.0(发布日期:20220725)
## 后端技术架构

1487
jeecg-boot/db/jeecgboot-mysql-5.7.sql

File diff suppressed because one or more lines are too long

12244
jeecg-boot/db/jeecgboot-oracle11g.sql

File diff suppressed because one or more lines are too long

26407
jeecg-boot/db/jeecgboot-sqlserver2019.sql

File diff suppressed because one or more lines are too long

93
jeecg-boot/db/tables_nacos.sql

File diff suppressed because one or more lines are too long

16
jeecg-boot/db/增量SQL/3.1.0升级到3.2.0增量脚本sql

@ -1,16 +0,0 @@
-- ----------------------------
-- Table structure for sys_role_index
-- ----------------------------
CREATE TABLE `sys_role_index` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`role_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色编码',
`url` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址',
`priority` int(11) NULL DEFAULT 0 COMMENT '优先级',
`status` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '状态0:无效 1:有效',
`create_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人登录名称',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建日期',
`update_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人登录名称',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新日期',
`sys_org_code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所属部门',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色首页表' ROW_FORMAT = Dynamic;

22
jeecg-boot/db/增量SQL/3.2升级到3.3增量脚本sql

@ -0,0 +1,22 @@
ALTER TABLE `sys_user`
MODIFY COLUMN `org_code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登录会话的机构编码' AFTER `phone`;
ALTER TABLE `sys_role_index`
ADD COLUMN `component` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组件' AFTER `url`,
ADD COLUMN `is_route` tinyint(1) NULL DEFAULT 1 COMMENT '是否路由菜单: 0:不是 1:是(默认值1)' AFTER `component`;
ALTER TABLE `jeecg_order_main`
ADD COLUMN `bpm_status` varchar(3) NULL COMMENT '流程状态' AFTER `update_time`;
UPDATE `sys_dict_item` SET `dict_id` = '4f69be5f507accea8d5df5f11346181a', `item_text` = '文本', `item_value` = '1', `description` = '', `sort_order` = 1, `status` = 1, `create_by` = 'admin', `create_time` = '2023-02-28 10:50:36', `update_by` = 'admin', `update_time` = '2022-07-04 16:29:21' WHERE `id` = '222705e11ef0264d4214affff1fb4ff9';
UPDATE `sys_dict_item` SET `dict_id` = '4f69be5f507accea8d5df5f11346181a', `item_text` = '富文本', `item_value` = '2', `description` = '', `sort_order` = 2, `status` = 1, `create_by` = 'admin', `create_time` = '2031-02-28 10:50:44', `update_by` = 'admin', `update_time` = '2022-07-04 16:29:30' WHERE `id` = '6a7a9e1403a7943aba69e54ebeff9762';
delete from sys_dict_item where id in ('1199607547704647681', '8bccb963e1cd9e8d42482c54cc609ca2');
update sys_sms_template set template_type = '2' where template_type='4';
update sys_sms_template set template_type = '1' where template_type='3';
ALTER TABLE `sys_sms_template`
ADD COLUMN `use_status` varchar(1) NULL COMMENT '是否使用中 1是0否' AFTER `update_by`;
ALTER TABLE `sys_sms`
MODIFY COLUMN `es_type` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发送方式:参考枚举MessageTypeEnum' AFTER `es_title`;

2
jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-cloud-api/pom.xml

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-boot-base-api</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.2.0</version>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

13
jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java

@ -536,4 +536,17 @@ public interface ISysBaseAPI extends CommonAPI {
@GetMapping("/sys/api/translateDictFromTableByKeys")
List<DictModel> translateDictFromTableByKeys(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("keys") String keys);
/**
* 发送模板消息
*/
@PostMapping("/sys/api/sendTemplateMessage")
void sendTemplateMessage(@RequestBody MessageDTO message);
/**
* 获取模板内容
* @param code
* @return
*/
@GetMapping("/sys/api/getTemplateContent")
String getTemplateContent(@RequestParam("code") String code);
}

9
jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/fallback/SysBaseAPIFallback.java

@ -278,6 +278,15 @@ public class SysBaseAPIFallback implements ISysBaseAPI {
return null;
}
@Override
public void sendTemplateMessage(MessageDTO message) {
}
@Override
public String getTemplateContent(String code) {
return null;
}
@Override
public void sendEmailMsg(String email,String title,String content) {

4
jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-cloud-api/src/main/java/org/jeecg/config/FeignConfig.java

@ -101,7 +101,7 @@
// log.info(" Feign request params sign: {}",sign);
// log.info("============================ [end] fegin api url ============================");
// requestTemplate.header(CommonConstant.X_SIGN, sign);
// requestTemplate.header(CommonConstant.X_TIMESTAMP, DateUtils.getCurrentTimestamp().toString());
// requestTemplate.header(CommonConstant.X_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
// } catch (IOException e) {
// e.printStackTrace();
// }
@ -146,7 +146,7 @@
// return new SpringEncoder(feignHttpMessageConverter());
// }
//
// @Bean
// @Bean("apiFeignDecoder")
// public Decoder feignDecoder() {
// return new SpringDecoder(feignHttpMessageConverter());
// }

2
jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-local-api/pom.xml

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-boot-base-api</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.2.0</version>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

13
jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/jeecg-system-local-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java

@ -338,4 +338,17 @@ public interface ISysBaseAPI extends CommonAPI {
*/
List<DictModel> loadDictItemByKeyword(String dictCode, String keyword, Integer pageSize);
/**
* 发送模板消息
* @param message
*/
void sendTemplateMessage(MessageDTO message);
/**
* 根据模板编码获取模板内容
* @param templateCode
* @return
*/
String getTemplateContent(String templateCode);
}

2
jeecg-boot/jeecg-boot-base/jeecg-boot-base-api/pom.xml

@ -5,7 +5,7 @@
<parent>
<artifactId>jeecg-boot-base</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.2.0</version>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

2
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/pom.xml

@ -4,7 +4,7 @@
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base</artifactId>
<version>3.2.0</version>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

15
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/dto/message/MessageDTO.java

@ -4,6 +4,7 @@ import lombok.Data;
import org.jeecg.common.constant.CommonConstant;
import java.io.Serializable;
import java.util.Map;
/**
* 普通消息
@ -72,4 +73,18 @@ public class MessageDTO implements Serializable {
this.category = category;
}
/**
* 模板消息对应的模板编码
*/
protected String templateCode;
/**
* 消息类型org.jeecg.common.constant.enums.MessageTypeEnum
*/
protected String type;
/**
* 解析模板内容 对应的数据
*/
protected Map<String, Object> data;
}

4
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java

@ -40,6 +40,8 @@ public class PermissionDataAspect {
@Autowired
private CommonAPI commonApi;
private static final String SPOT_DO = ".do";
@Pointcut("@annotation(org.jeecg.common.aspect.annotation.PermissionData)")
public void pointCut() {
@ -113,7 +115,7 @@ public class PermissionDataAspect {
requestPath = requestPath.substring(0, requestPath.indexOf("&"));
}
if(requestPath.indexOf(QueryRuleEnum.EQ.getValue())!=-1){
if(requestPath.indexOf(CommonConstant.SPOT_DO)!=-1){
if(requestPath.indexOf(SPOT_DO)!=-1){
requestPath = requestPath.substring(0,requestPath.indexOf(".do")+3);
}else{
requestPath = requestPath.substring(0,requestPath.indexOf("?"));

20
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/DynamicTable.java

@ -0,0 +1,20 @@
package org.jeecg.common.aspect.annotation;
import java.lang.annotation.*;
/**
* 动态table切换
*
* @author :zyf
* @date:2020-04-25
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicTable {
/**
* 需要动态解析的表名
* @return
*/
String value();
}

234
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/CommonConstant.java

@ -9,320 +9,321 @@ public interface CommonConstant {
/**
* 正常状态
*/
public static final Integer STATUS_NORMAL = 0;
Integer STATUS_NORMAL = 0;
/**
* 禁用状态
*/
public static final Integer STATUS_DISABLE = -1;
Integer STATUS_DISABLE = -1;
/**
* 删除标志
*/
public static final Integer DEL_FLAG_1 = 1;
Integer DEL_FLAG_1 = 1;
/**
* 未删除
*/
public static final Integer DEL_FLAG_0 = 0;
Integer DEL_FLAG_0 = 0;
/**
* 系统日志类型 登录
*/
public static final int LOG_TYPE_1 = 1;
int LOG_TYPE_1 = 1;
/**
* 系统日志类型 操作
*/
public static final int LOG_TYPE_2 = 2;
int LOG_TYPE_2 = 2;
/**
* 操作日志类型 查询
*/
public static final int OPERATE_TYPE_1 = 1;
int OPERATE_TYPE_1 = 1;
/**
* 操作日志类型 添加
*/
public static final int OPERATE_TYPE_2 = 2;
int OPERATE_TYPE_2 = 2;
/**
* 操作日志类型 更新
*/
public static final int OPERATE_TYPE_3 = 3;
int OPERATE_TYPE_3 = 3;
/**
* 操作日志类型 删除
*/
public static final int OPERATE_TYPE_4 = 4;
int OPERATE_TYPE_4 = 4;
/**
* 操作日志类型 倒入
*/
public static final int OPERATE_TYPE_5 = 5;
int OPERATE_TYPE_5 = 5;
/**
* 操作日志类型 导出
*/
public static final int OPERATE_TYPE_6 = 6;
int OPERATE_TYPE_6 = 6;
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
public static final Integer SC_OK_200 = 200;
Integer SC_OK_200 = 200;
/**访问权限认证未通过 510*/
public static final Integer SC_JEECG_NO_AUTHZ=510;
Integer SC_JEECG_NO_AUTHZ=510;
/** 登录用户Shiro权限缓存KEY前缀 */
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
/** 登录用户Token令牌缓存KEY前缀 */
public static final String PREFIX_USER_TOKEN = "prefix_user_token_";
String PREFIX_USER_TOKEN = "prefix_user_token_";
// /** Token缓存时间:3600秒即一小时 */
// public static final int TOKEN_EXPIRE_TIME = 3600;
// int TOKEN_EXPIRE_TIME = 3600;
/** 登录二维码 */
public static final String LOGIN_QRCODE_PRE = "QRCODELOGIN:";
public static final String LOGIN_QRCODE = "LQ:";
String LOGIN_QRCODE_PRE = "QRCODELOGIN:";
String LOGIN_QRCODE = "LQ:";
/** 登录二维码token */
public static final String LOGIN_QRCODE_TOKEN = "LQT:";
String LOGIN_QRCODE_TOKEN = "LQT:";
/**
* 0一级菜单
*/
public static final Integer MENU_TYPE_0 = 0;
Integer MENU_TYPE_0 = 0;
/**
* 1子菜单
*/
public static final Integer MENU_TYPE_1 = 1;
Integer MENU_TYPE_1 = 1;
/**
* 2按钮权限
*/
public static final Integer MENU_TYPE_2 = 2;
Integer MENU_TYPE_2 = 2;
/**通告对象类型(USER:指定用户,ALL:全体用户)*/
public static final String MSG_TYPE_UESR = "USER";
public static final String MSG_TYPE_ALL = "ALL";
String MSG_TYPE_UESR = "USER";
String MSG_TYPE_ALL = "ALL";
/**发布状态(0未发布,1已发布,2已撤销)*/
public static final String NO_SEND = "0";
public static final String HAS_SEND = "1";
public static final String HAS_CANCLE = "2";
String NO_SEND = "0";
String HAS_SEND = "1";
String HAS_CANCLE = "2";
/**阅读状态(0未读,1已读)*/
public static final String HAS_READ_FLAG = "1";
public static final String NO_READ_FLAG = "0";
String HAS_READ_FLAG = "1";
String NO_READ_FLAG = "0";
/**优先级(L低,M中,H高)*/
public static final String PRIORITY_L = "L";
public static final String PRIORITY_M = "M";
public static final String PRIORITY_H = "H";
String PRIORITY_L = "L";
String PRIORITY_M = "M";
String PRIORITY_H = "H";
/**
* 短信模板方式 0 .登录模板1.注册模板2.忘记密码模板
*/
public static final String SMS_TPL_TYPE_0 = "0";
public static final String SMS_TPL_TYPE_1 = "1";
public static final String SMS_TPL_TYPE_2 = "2";
String SMS_TPL_TYPE_0 = "0";
String SMS_TPL_TYPE_1 = "1";
String SMS_TPL_TYPE_2 = "2";
/**
* 状态(0无效1有效)
*/
public static final String STATUS_0 = "0";
public static final String STATUS_1 = "1";
String STATUS_0 = "0";
String STATUS_1 = "1";
/**
* 同步工作流引擎1同步0不同步
*/
public static final Integer ACT_SYNC_1 = 1;
public static final Integer ACT_SYNC_0 = 0;
Integer ACT_SYNC_1 = 1;
Integer ACT_SYNC_0 = 0;
/**
* 消息类型1:通知公告2:系统消息
*/
public static final String MSG_CATEGORY_1 = "1";
public static final String MSG_CATEGORY_2 = "2";
String MSG_CATEGORY_1 = "1";
String MSG_CATEGORY_2 = "2";
/**
* 是否配置菜单的数据权限 1是0否
*/
public static final Integer RULE_FLAG_0 = 0;
public static final Integer RULE_FLAG_1 = 1;
Integer RULE_FLAG_0 = 0;
Integer RULE_FLAG_1 = 1;
/**
* 是否用户已被冻结 1正常(解冻) 2冻结
*/
public static final Integer USER_UNFREEZE = 1;
public static final Integer USER_FREEZE = 2;
Integer USER_UNFREEZE = 1;
Integer USER_FREEZE = 2;
/**字典翻译文本后缀*/
public static final String DICT_TEXT_SUFFIX = "_dictText";
String DICT_TEXT_SUFFIX = "_dictText";
/**
* 表单设计器主表类型
*/
public static final Integer DESIGN_FORM_TYPE_MAIN = 1;
Integer DESIGN_FORM_TYPE_MAIN = 1;
/**
* 表单设计器子表表类型
*/
public static final Integer DESIGN_FORM_TYPE_SUB = 2;
Integer DESIGN_FORM_TYPE_SUB = 2;
/**
* 表单设计器URL授权通过
*/
public static final Integer DESIGN_FORM_URL_STATUS_PASSED = 1;
Integer DESIGN_FORM_URL_STATUS_PASSED = 1;
/**
* 表单设计器URL授权未通过
*/
public static final Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2;
Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2;
/**
* 表单设计器新增 Flag
*/
public static final String DESIGN_FORM_URL_TYPE_ADD = "add";
String DESIGN_FORM_URL_TYPE_ADD = "add";
/**
* 表单设计器修改 Flag
*/
public static final String DESIGN_FORM_URL_TYPE_EDIT = "edit";
String DESIGN_FORM_URL_TYPE_EDIT = "edit";
/**
* 表单设计器详情 Flag
*/
public static final String DESIGN_FORM_URL_TYPE_DETAIL = "detail";
String DESIGN_FORM_URL_TYPE_DETAIL = "detail";
/**
* 表单设计器复用数据 Flag
*/
public static final String DESIGN_FORM_URL_TYPE_REUSE = "reuse";
String DESIGN_FORM_URL_TYPE_REUSE = "reuse";
/**
* 表单设计器编辑 Flag 已弃用
*/
public static final String DESIGN_FORM_URL_TYPE_VIEW = "view";
String DESIGN_FORM_URL_TYPE_VIEW = "view";
/**
* online参数值设置Y, N
*/
public static final String ONLINE_PARAM_VAL_IS_TURE = "Y";
public static final String ONLINE_PARAM_VAL_IS_FALSE = "N";
String ONLINE_PARAM_VAL_IS_TURE = "Y";
String ONLINE_PARAM_VAL_IS_FALSE = "N";
/**
* 文件上传类型本地localMiniominio阿里云alioss
*/
public static final String UPLOAD_TYPE_LOCAL = "local";
public static final String UPLOAD_TYPE_MINIO = "minio";
public static final String UPLOAD_TYPE_OSS = "alioss";
String UPLOAD_TYPE_LOCAL = "local";
String UPLOAD_TYPE_MINIO = "minio";
String UPLOAD_TYPE_OSS = "alioss";
/**
* 文档上传自定义桶名称
*/
public static final String UPLOAD_CUSTOM_BUCKET = "eoafile";
String UPLOAD_CUSTOM_BUCKET = "eoafile";
/**
* 文档上传自定义路径
*/
public static final String UPLOAD_CUSTOM_PATH = "eoafile";
String UPLOAD_CUSTOM_PATH = "eoafile";
/**
* 文件外链接有效天数
*/
public static final Integer UPLOAD_EFFECTIVE_DAYS = 1;
Integer UPLOAD_EFFECTIVE_DAYS = 1;
/**
* 员工身份 1:普通员工 2:上级
*/
public static final Integer USER_IDENTITY_1 = 1;
public static final Integer USER_IDENTITY_2 = 2;
Integer USER_IDENTITY_1 = 1;
Integer USER_IDENTITY_2 = 2;
/** sys_user 表 username 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_USER_USERNAME = "uniq_sys_user_username";
String SQL_INDEX_UNIQ_SYS_USER_USERNAME = "uniq_sys_user_username";
/** sys_user 表 work_no 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_USER_WORK_NO = "uniq_sys_user_work_no";
String SQL_INDEX_UNIQ_SYS_USER_WORK_NO = "uniq_sys_user_work_no";
/** sys_user 表 phone 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_USER_PHONE = "uniq_sys_user_phone";
String SQL_INDEX_UNIQ_SYS_USER_PHONE = "uniq_sys_user_phone";
/** 达梦数据库升提示。违反表[SYS_USER]唯一性约束 */
public static final String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束";
String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束";
/** sys_user 表 email 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_USER_EMAIL = "uniq_sys_user_email";
String SQL_INDEX_UNIQ_SYS_USER_EMAIL = "uniq_sys_user_email";
/** sys_quartz_job 表 job_class_name 唯一键索引 */
public static final String SQL_INDEX_UNIQ_JOB_CLASS_NAME = "uniq_job_class_name";
String SQL_INDEX_UNIQ_JOB_CLASS_NAME = "uniq_job_class_name";
/** sys_position 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_CODE = "uniq_code";
String SQL_INDEX_UNIQ_CODE = "uniq_code";
/** sys_role 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_ROLE_CODE = "uniq_sys_role_role_code";
String SQL_INDEX_UNIQ_SYS_ROLE_CODE = "uniq_sys_role_role_code";
/** sys_depart 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_DEPART_ORG_CODE = "uniq_depart_org_code";
String SQL_INDEX_UNIQ_DEPART_ORG_CODE = "uniq_depart_org_code";
/** sys_category 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_CATEGORY_CODE = "idx_sc_code";
String SQL_INDEX_UNIQ_CATEGORY_CODE = "idx_sc_code";
/**
* 在线聊天 是否为默认分组
*/
public static final String IM_DEFAULT_GROUP = "1";
String IM_DEFAULT_GROUP = "1";
/**
* 在线聊天 图片文件保存路径
*/
public static final String IM_UPLOAD_CUSTOM_PATH = "imfile";
String IM_UPLOAD_CUSTOM_PATH = "imfile";
/**
* 在线聊天 用户状态
*/
public static final String IM_STATUS_ONLINE = "online";
String IM_STATUS_ONLINE = "online";
/**
* 在线聊天 SOCKET消息类型
*/
public static final String IM_SOCKET_TYPE = "chatMessage";
String IM_SOCKET_TYPE = "chatMessage";
/**
* 在线聊天 是否开启默认添加好友 1是 0否
*/
public static final String IM_DEFAULT_ADD_FRIEND = "1";
String IM_DEFAULT_ADD_FRIEND = "1";
/**
* 在线聊天 用户好友缓存前缀
*/
public static final String IM_PREFIX_USER_FRIEND_CACHE = "sys:cache:im:im_prefix_user_friend_";
String IM_PREFIX_USER_FRIEND_CACHE = "sys:cache:im:im_prefix_user_friend_";
/**
* 考勤补卡业务状态 1同意 2不同意
*/
public static final String SIGN_PATCH_BIZ_STATUS_1 = "1";
public static final String SIGN_PATCH_BIZ_STATUS_2 = "2";
String SIGN_PATCH_BIZ_STATUS_1 = "1";
String SIGN_PATCH_BIZ_STATUS_2 = "2";
/**
* 公文文档上传自定义路径
*/
public static final String UPLOAD_CUSTOM_PATH_OFFICIAL = "officialdoc";
String UPLOAD_CUSTOM_PATH_OFFICIAL = "officialdoc";
/**
* 公文文档下载自定义路径
*/
public static final String DOWNLOAD_CUSTOM_PATH_OFFICIAL = "officaldown";
String DOWNLOAD_CUSTOM_PATH_OFFICIAL = "officaldown";
/**
* WPS存储值类别(1 code文号 2 textWPS模板还是公文发文模板)
*/
public static final String WPS_TYPE_1="1";
public static final String WPS_TYPE_2="2";
String WPS_TYPE_1="1";
String WPS_TYPE_2="2";
public final static String X_ACCESS_TOKEN = "X-Access-Token";
public final static String X_SIGN = "X-Sign";
public final static String X_TIMESTAMP = "X-TIMESTAMP";
public final static String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!";
String X_ACCESS_TOKEN = "X-Access-Token";
String X_SIGN = "X-Sign";
String X_TIMESTAMP = "X-TIMESTAMP";
String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!";
String X_FORWARDED_SCHEME = "X-Forwarded-Scheme";
/**
* 多租户 请求头
*/
public final static String TENANT_ID = "tenant-id";
String TENANT_ID = "tenant-id";
/**
* 微服务读取配置文件属性 服务地址
*/
public final static String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr";
String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr";
/**
* 第三方登录 验证密码/创建用户 都需要设置一个操作码 防止被恶意调用
*/
public final static String THIRD_LOGIN_CODE = "third_login_code";
String THIRD_LOGIN_CODE = "third_login_code";
/**
* 第三方APP同步方向本地 --> 第三方APP
@ -361,16 +362,43 @@ public interface CommonConstant {
/**String 类型的空值*/
String STRING_NULL = "null";
/**java.util.Date 包*/
String JAVA_UTIL_DATE = "java.util.Date";
/**前端vue3版本Header参数名*/
String VERSION="X-Version";
/**.do*/
String SPOT_DO = ".do";
/**存储在线程变量里的动态表名*/
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
/**
* http:// http协议
*/
String HTTP_PROTOCOL = "http://";
/**
* https:// https协议
*/
String HTTPS_PROTOCOL = "https://";
/**前端vue版本标识*/
String VERSION="X-Version";
/** 部门表唯一key,id */
String DEPART_KEY_ID = "id";
/** 部门表唯一key,orgCode */
String DEPART_KEY_ORG_CODE = "orgCode";
/**
* 发消息 会传递一些信息到map
*/
String NOTICE_MSG_SUMMARY = "NOTICE_MSG_SUMMARY";
/**前端vue版本*/
String VERSION_VUE3="vue3";
/**
* 发消息 会传递一个业务ID到map
*/
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
/**
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
*/
String LOGIN_TOKEN = "{LOGIN_TOKEN}";
/**
* 模板消息中 跳转地址的对应的key
*/
String MSG_HREF_URL = "url";
}

10
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DataBaseConstant.java

@ -153,4 +153,14 @@ public interface DataBaseConstant {
* sql语句 where
*/
String SQL_WHERE = "where";
/**
* sql语句 asc
*/
String SQL_ASC = "asc";
/**
* sqlserver数据库,中间有空格
*/
String DB_TYPE_SQL_SERVER_BLANK = "sql server";
}

16
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/DynamicTableConstant.java

@ -0,0 +1,16 @@
package org.jeecg.common.constant;
/**
* 动态切换表配置常量
*
* @author: scott
* @date: 2022年04月25日 22:30
*/
public class DynamicTableConstant {
/**
* 角色首页配置表
* vue2表名: sys_role_index
* vue3表名: sys_role_index_vue3
*/
public static final String SYS_ROLE_INDEX = "sys_role_index";
}

18
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/ProvinceCityArea.java

@ -35,9 +35,13 @@ public class ProvinceCityArea {
this.initAreaList();
if(areaList!=null && areaList.size()>0){
for(int i=areaList.size()-1;i>=0;i--){
if(text.indexOf(areaList.get(i).getText())>=0){
//update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
String areaText = areaList.get(i).getText();
String cityText = areaList.get(i).getAheadText();
if(text.indexOf(areaText)>=0 && (cityText!=null && text.indexOf(cityText)>=0)){
return areaList.get(i).getId();
}
//update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
}
}
return null;
@ -145,6 +149,9 @@ public class ProvinceCityArea {
for(String areaKey:areaJson.keySet()){
//System.out.println("········"+areaKey);
Area area = new Area(areaKey,areaJson.getString(areaKey),cityKey);
//update-begin-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
area.setAheadText(cityJson.getString(cityKey));
//update-end-author:taoyan date:2022-5-24 for:VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
this.areaList.add(area);
}
}
@ -180,6 +187,8 @@ public class ProvinceCityArea {
String id;
String text;
String pid;
// 用于存储上级文本数据,区的上级文本 是市的数据
String aheadText;
public Area(String id,String text,String pid){
this.id = id;
@ -198,5 +207,12 @@ public class ProvinceCityArea {
public String getPid() {
return pid;
}
public String getAheadText() {
return aheadText;
}
public void setAheadText(String aheadText) {
this.aheadText = aheadText;
}
}
}

30
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/SymbolConstant.java

@ -86,4 +86,34 @@ public class SymbolConstant {
* 符号 &
*/
public static final String AND = "&";
/**
* 符号../
*/
public static final String SPOT_SINGLE_SLASH = "../";
/**
* 符号..\\
*/
public static final String SPOT_DOUBLE_BACKSLASH = "..\\";
/**
* 系统变量前缀 #{
*/
public static final String SYS_VAR_PREFIX = "#{";
/**
* 符号 {{
*/
public static final String DOUBLE_LEFT_CURLY_BRACKET = "{{";
/**
* 符号[
*/
public static final String SQUARE_BRACKETS_LEFT = "[";
/**
* 符号]
*/
public static final String SQUARE_BRACKETS_RIGHT = "]";
}

9
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/CgformEnum.java

@ -19,14 +19,15 @@ public enum CgformEnum {
* 多表
*/
MANY(2, "many", "/jeecg/code-template-online", "default.onetomany", "经典风格"),
/**
* 多表
*/
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格"),
/**
* 多表jvxe风格
* */
JVXE_TABLE(2, "jvxe", "/jeecg/code-template-online", "jvxe.onetomany", "JVXE风格"),
/**
* 多表
*/
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格"),
/**
* 多表内嵌子表风格
*/

2
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/LowAppAopEnum.java

@ -15,6 +15,8 @@ public enum LowAppAopEnum {
* 删除方法包含单个和批量删除
*/
DELETE,
/** 复制表单操作 */
COPY,
/**
* Online表单专用数据库表转Online表单

68
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/enums/MessageTypeEnum.java

@ -0,0 +1,68 @@
package org.jeecg.common.constant.enums;
import org.jeecg.common.system.annotation.EnumDict;
import org.jeecg.common.system.vo.DictModel;
import java.util.ArrayList;
import java.util.List;
/**
* 消息类型
* @author: jeecg-boot
*/
@EnumDict("messageType")
public enum MessageTypeEnum {
XT("system", "系统消息"),
YJ("email", "邮件消息"),
DD("dingtalk", "钉钉消息"),
QYWX("wechat_enterprise", "企业微信");
MessageTypeEnum(String type, String note){
this.type = type;
this.note = note;
}
/**
* 消息类型
*/
String type;
/**
* 类型说明
*/
String note;
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
/**
* 获取字典数据
* @return
*/
public static List<DictModel> getDictList(){
List<DictModel> list = new ArrayList<>();
DictModel dictModel = null;
for(MessageTypeEnum e: MessageTypeEnum.values()){
dictModel = new DictModel();
dictModel.setValue(e.getType());
dictModel.setText(e.getNote());
list.add(dictModel);
}
return list;
}
}

20
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveDecode.java

@ -0,0 +1,20 @@
package org.jeecg.common.desensitization.annotation;
import java.lang.annotation.*;
/**
* 解密注解
*
* 在方法上定义 将方法返回对象中的敏感字段 解密需要注意的是如果没有加密过解密会出问题返回原字符串
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SensitiveDecode {
/**
* 指明需要脱敏的实体类class
* @return
*/
Class entity() default Object.class;
}

20
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveEncode.java

@ -0,0 +1,20 @@
package org.jeecg.common.desensitization.annotation;
import java.lang.annotation.*;
/**
* 加密注解
*
* 在方法上声明 将方法返回对象中的敏感字段 加密/格式化
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SensitiveEncode {
/**
* 指明需要脱敏的实体类class
* @return
*/
Class entity() default Object.class;
}

21
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/annotation/SensitiveField.java

@ -0,0 +1,21 @@
package org.jeecg.common.desensitization.annotation;
import org.jeecg.common.desensitization.enums.SensitiveEnum;
import java.lang.annotation.*;
/**
* 在字段上定义 标识字段存储的信息是敏感的
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveField {
/**
* 不同类型处理不同
* @return
*/
SensitiveEnum type() default SensitiveEnum.ENCODE;
}

81
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/aspect/SensitiveDataAspect.java

@ -0,0 +1,81 @@
package org.jeecg.common.desensitization.aspect;
import lombok.extern.slf4j.Slf4j;
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.jeecg.common.desensitization.annotation.SensitiveDecode;
import org.jeecg.common.desensitization.annotation.SensitiveEncode;
import org.jeecg.common.desensitization.util.SensitiveInfoUtil;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
/**
* 敏感数据切面处理类
* @Author taoYan
* @Date 2022/4/20 17:45
**/
@Slf4j
@Aspect
@Component
public class SensitiveDataAspect {
/**
* 定义切点Pointcut
*/
@Pointcut("@annotation(org.jeecg.common.desensitization.annotation.SensitiveEncode) || @annotation(org.jeecg.common.desensitization.annotation.SensitiveDecode)")
public void sensitivePointCut() {
}
@Around("sensitivePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 处理结果
Object result = point.proceed();
if(result == null){
return result;
}
Class resultClass = result.getClass();
log.debug(" resultClass = {}" , resultClass);
if(resultClass.isPrimitive()){
//是基本类型 直接返回 不需要处理
return result;
}
// 获取方法注解信息:是哪个实体、是加密还是解密
boolean isEncode = true;
Class entity = null;
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
SensitiveEncode encode = method.getAnnotation(SensitiveEncode.class);
if(encode==null){
SensitiveDecode decode = method.getAnnotation(SensitiveDecode.class);
if(decode!=null){
entity = decode.entity();
isEncode = false;
}
}else{
entity = encode.entity();
}
long startTime=System.currentTimeMillis();
if(resultClass.equals(entity) || entity.equals(Object.class)){
// 方法返回实体和注解的entity一样,如果注解没有申明entity属性则认为是(方法返回实体和注解的entity一样)
SensitiveInfoUtil.handlerObject(result, isEncode);
} else if(result instanceof List){
// 方法返回List<实体>
SensitiveInfoUtil.handleList(result, entity, isEncode);
}else{
// 方法返回一个对象
SensitiveInfoUtil.handleNestedObject(result, entity, isEncode);
}
long endTime=System.currentTimeMillis();
log.info((isEncode ? "加密操作," : "解密操作,") + "Aspect程序耗时:" + (endTime - startTime) + "ms");
return result;
}
}

55
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/enums/SensitiveEnum.java

@ -0,0 +1,55 @@
package org.jeecg.common.desensitization.enums;
/**
* 敏感字段信息类型
*/
public enum SensitiveEnum {
/**
* 加密
*/
ENCODE,
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证号
*/
ID_CARD,
/**
* 座机号
*/
FIXED_PHONE,
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 银行卡
*/
BANK_CARD,
/**
* 公司开户银行联号
*/
CNAPS_CODE;
}

362
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/desensitization/util/SensitiveInfoUtil.java

@ -0,0 +1,362 @@
package org.jeecg.common.desensitization.util;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.desensitization.annotation.SensitiveField;
import org.jeecg.common.desensitization.enums.SensitiveEnum;
import org.jeecg.common.util.encryption.AesEncryptUtil;
import org.jeecg.common.util.oConvertUtils;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.List;
/**
* 敏感信息处理工具类
* @author taoYan
* @date 2022/4/20 18:01
**/
@Slf4j
public class SensitiveInfoUtil {
/**
* 处理嵌套对象
* @param obj 方法返回值
* @param entity 实体class
* @param isEncode 是否加密true: 加密操作 / false:解密操作
* @throws IllegalAccessException
*/
public static void handleNestedObject(Object obj, Class entity, boolean isEncode) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if(field.getType().isPrimitive()){
continue;
}
if(field.getType().equals(entity)){
// 对象里面是实体
field.setAccessible(true);
Object nestedObject = field.get(obj);
handlerObject(nestedObject, isEncode);
break;
}else{
// 对象里面是List<实体>
if(field.getGenericType() instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType)field.getGenericType();
if(pt.getRawType().equals(List.class)){
if(pt.getActualTypeArguments()[0].equals(entity)){
field.setAccessible(true);
Object nestedObject = field.get(obj);
handleList(nestedObject, entity, isEncode);
break;
}
}
}
}
}
}
/**
* 处理Object
* @param obj 方法返回值
* @param isEncode 是否加密true: 加密操作 / false:解密操作
* @return
* @throws IllegalAccessException
*/
public static Object handlerObject(Object obj, boolean isEncode) throws IllegalAccessException {
log.debug(" obj --> "+ obj.toString());
long startTime=System.currentTimeMillis();
if (oConvertUtils.isEmpty(obj)) {
return obj;
}
// 判断是不是一个对象
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
boolean isSensitiveField = field.isAnnotationPresent(SensitiveField.class);
if(isSensitiveField){
// 必须有SensitiveField注解 才作处理
if(field.getType().isAssignableFrom(String.class)){
//必须是字符串类型 才作处理
field.setAccessible(true);
String realValue = (String) field.get(obj);
if(realValue==null || "".equals(realValue)){
continue;
}
SensitiveField sf = field.getAnnotation(SensitiveField.class);
if(isEncode==true){
//加密
String value = SensitiveInfoUtil.getEncodeData(realValue, sf.type());
field.set(obj, value);
}else{
//解密只处理 encode类型的
if(sf.type().equals(SensitiveEnum.ENCODE)){
String value = SensitiveInfoUtil.getDecodeData(realValue);
field.set(obj, value);
}
}
}
}
}
//long endTime=System.currentTimeMillis();
//log.info((isEncode ? "加密操作," : "解密操作,") + "当前程序耗时:" + (endTime - startTime) + "ms");
return obj;
}
/**
* 处理 List<实体>
* @param obj
* @param entity
* @param isEncodetrue: 加密操作 / false:解密操作
*/
public static void handleList(Object obj, Class entity, boolean isEncode){
List list = (List)obj;
if(list.size()>0){
Object first = list.get(0);
if(first.getClass().equals(entity)){
for(int i=0; i<list.size(); i++){
Object temp = list.get(i);
try {
handlerObject(temp, isEncode);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 处理数据 获取解密后的数据
* @param data
* @return
*/
public static String getDecodeData(String data){
String result = null;
try {
result = AesEncryptUtil.desEncrypt(data);
} catch (Exception exception) {
log.warn("数据解密错误,原数据:"+data);
}
//解决debug模式下,加解密失效导致中文被解密变成空的问题
if(oConvertUtils.isEmpty(result) && oConvertUtils.isNotEmpty(data)){
result = data;
}
return result;
}
/**
* 处理数据 获取加密后的数据 或是格式化后的数据
* @param data 字符串
* @param sensitiveEnum 类型
* @return 处理后的字符串
*/
public static String getEncodeData(String data, SensitiveEnum sensitiveEnum){
String result;
switch (sensitiveEnum){
case ENCODE:
try {
result = AesEncryptUtil.encrypt(data);
} catch (Exception exception) {
log.error("数据加密错误", exception.getMessage());
result = data;
}
break;
case CHINESE_NAME:
result = chineseName(data);
break;
case ID_CARD:
result = idCardNum(data);
break;
case FIXED_PHONE:
result = fixedPhone(data);
break;
case MOBILE_PHONE:
result = mobilePhone(data);
break;
case ADDRESS:
result = address(data, 3);
break;
case EMAIL:
result = email(data);
break;
case BANK_CARD:
result = bankCard(data);
break;
case CNAPS_CODE:
result = cnapsCode(data);
break;
default:
result = data;
}
return result;
}
/**
* [中文姓名] 只显示第一个汉字其他隐藏为2个星号
* @param fullName 全名
* @return <例子**>
*/
private static String chineseName(String fullName) {
if (oConvertUtils.isEmpty(fullName)) {
return "";
}
return formatRight(fullName, 1);
}
/**
* [中文姓名] 只显示第一个汉字其他隐藏为2个星号
* @param familyName
* @param firstName
* @return <例子**>
*/
private static String chineseName(String familyName, String firstName) {
if (oConvertUtils.isEmpty(familyName) || oConvertUtils.isEmpty(firstName)) {
return "";
}
return chineseName(familyName + firstName);
}
/**
* [身份证号] 显示最后四位其他隐藏共计18位或者15位
* @param id 身份证号
* @return <例子*************5762>
*/
private static String idCardNum(String id) {
if (oConvertUtils.isEmpty(id)) {
return "";
}
return formatLeft(id, 4);
}
/**
* [固定电话] 后四位其他隐藏
* @param num 固定电话
* @return <例子****1234>
*/
private static String fixedPhone(String num) {
if (oConvertUtils.isEmpty(num)) {
return "";
}
return formatLeft(num, 4);
}
/**
* [手机号码] 前三位后四位其他隐藏
* @param num 手机号码
* @return <例子:138******1234>
*/
private static String mobilePhone(String num) {
if (oConvertUtils.isEmpty(num)) {
return "";
}
int len = num.length();
if(len<11){
return num;
}
return formatBetween(num, 3, 4);
}
/**
* [地址] 只显示到地区不显示详细地址我们要对个人信息增强保护
* @param address 地址
* @param sensitiveSize 敏感信息长度
* @return <例子北京市海淀区****>
*/
private static String address(String address, int sensitiveSize) {
if (oConvertUtils.isEmpty(address)) {
return "";
}
int len = address.length();
if(len<sensitiveSize){
return address;
}
return formatRight(address, sensitiveSize);
}
/**
* [电子邮箱] 邮箱前缀仅显示第一个字母前缀其他隐藏用星号代替@及后面的地址显示
* @param email 电子邮箱
* @return <例子:g**@163.com>
*/
private static String email(String email) {
if (oConvertUtils.isEmpty(email)) {
return "";
}
int index = email.indexOf("@");
if (index <= 1){
return email;
}
String begin = email.substring(0, 1);
String end = email.substring(index);
String stars = "**";
return begin + stars + end;
}
/**
* [银行卡号] 前六位后四位其他用星号隐藏每位1个星号
* @param cardNum 银行卡号
* @return <例子:6222600**********1234>
*/
private static String bankCard(String cardNum) {
if (oConvertUtils.isEmpty(cardNum)) {
return "";
}
return formatBetween(cardNum, 6, 4);
}
/**
* [公司开户银行联号] 公司开户银行联行号,显示前两位其他用星号隐藏每位1个星号
* @param code 公司开户银行联号
* @return <例子:12********>
*/
private static String cnapsCode(String code) {
if (oConvertUtils.isEmpty(code)) {
return "";
}
return formatRight(code, 2);
}
/**
* 将右边的格式化成*
* @param str 字符串
* @param reservedLength 保留长度
* @return 格式化后的字符串
*/
private static String formatRight(String str, int reservedLength){
String name = str.substring(0, reservedLength);
String stars = String.join("", Collections.nCopies(str.length()-reservedLength, "*"));
return name + stars;
}
/**
* 将左边的格式化成*
* @param str 字符串
* @param reservedLength 保留长度
* @return 格式化后的字符串
*/
private static String formatLeft(String str, int reservedLength){
int len = str.length();
String show = str.substring(len-reservedLength);
String stars = String.join("", Collections.nCopies(len-reservedLength, "*"));
return stars + show;
}
/**
* 将中间的格式化成*
* @param str 字符串
* @param beginLen 开始保留长度
* @param endLen 结尾保留长度
* @return 格式化后的字符串
*/
private static String formatBetween(String str, int beginLen, int endLen){
int len = str.length();
String begin = str.substring(0, beginLen);
String end = str.substring(len-endLen);
String stars = String.join("", Collections.nCopies(len-beginLen-endLen, "*"));
return begin + stars + end;
}
}

3
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java

@ -123,7 +123,8 @@ public class JeecgBootExceptionHandler {
@ExceptionHandler(DataIntegrityViolationException.class)
public Result<?> handleDataIntegrityViolationException(DataIntegrityViolationException e) {
log.error(e.getMessage(), e);
return Result.error("字段太长,超出数据库字段的长度");
//【issues/3624】数据库执行异常handleDataIntegrityViolationException提示有误 #3624
return Result.error("执行数据库异常,违反了完整性例如:违反惟一约束、违反非空限制、字段内容超出长度等");
}
@ExceptionHandler(PoolException.class)

19
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/annotation/EnumDict.java

@ -0,0 +1,19 @@
package org.jeecg.common.system.annotation;
import java.lang.annotation.*;
/**
* 将枚举类转化成字典数据
* @Author taoYan
* @Date 2022/7/8 10:34
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnumDict {
/**
* 作为字典数据的唯一编码
*/
String value() default "";
}

31
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java

@ -53,18 +53,14 @@ public class JeecgController<T, S extends IService<T>> {
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
// Step.2 获取导出数据
List<T> pageList = service.list(queryWrapper);
List<T> exportList = null;
// 过滤选中数据
String selections = request.getParameter("selections");
if (oConvertUtils.isNotEmpty(selections)) {
List<String> selectionList = Arrays.asList(selections.split(","));
exportList = pageList.stream().filter(item -> selectionList.contains(getId(item))).collect(Collectors.toList());
} else {
exportList = pageList;
queryWrapper.in("id",selectionList);
}
// Step.2 获取导出数据
List<T> exportList = service.list(queryWrapper);
// Step.3 AutoPoi 导出Excel
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
@ -97,21 +93,20 @@ public class JeecgController<T, S extends IService<T>> {
// Step.2 计算分页sheet数据
double total = service.count();
int count = (int)Math.ceil(total/pageNum);
// Step.3 多sheet处理
//update-begin-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 ---
// Step.3 过滤选中数据
String selections = request.getParameter("selections");
if (oConvertUtils.isNotEmpty(selections)) {
List<String> selectionList = Arrays.asList(selections.split(","));
queryWrapper.in("id",selectionList);
}
//update-end-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 ---
// Step.4 多sheet处理
List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
for (int i = 1; i <=count ; i++) {
Page<T> page = new Page<T>(i, pageNum);
IPage<T> pageList = service.page(page, queryWrapper);
List<T> records = pageList.getRecords();
List<T> exportList = null;
// 过滤选中数据
String selections = request.getParameter("selections");
if (oConvertUtils.isNotEmpty(selections)) {
List<String> selectionList = Arrays.asList(selections.split(","));
exportList = records.stream().filter(item -> selectionList.contains(getId(item))).collect(Collectors.toList());
} else {
exportList = records;
}
List<T> exportList = pageList.getRecords();
Map<String, Object> map = new HashMap<>(5);
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,upLoadPath);
exportParams.setType(ExcelType.XSSF);

79
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java

@ -16,6 +16,7 @@ import org.apache.commons.beanutils.PropertyUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.util.JeecgDataAutorUtils;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
@ -192,7 +193,7 @@ public class QueryGenerator {
}
}
// 排序逻辑 处理
doMultiFieldsOrder(queryWrapper, parameterMap);
doMultiFieldsOrder(queryWrapper, parameterMap, fieldColumnMap.keySet());
//高级查询
doSuperQuery(queryWrapper, parameterMap, fieldColumnMap);
@ -228,8 +229,7 @@ public class QueryGenerator {
}
}
/**多字段排序 TODO 需要修改前端*/
private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap) {
private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap, Set<String> allFields) {
String column=null,order=null;
if(parameterMap!=null&& parameterMap.containsKey(ORDER_COLUMN)) {
column = parameterMap.get(ORDER_COLUMN)[0];
@ -243,6 +243,15 @@ public class QueryGenerator {
if(column.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
column = column.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
}
//update-begin-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效
//判断column是不是当前实体的
log.info("当前字段有:"+ allFields);
if (!allColumnExist(column, allFields)) {
throw new JeecgBootException("请注意,将要排序的列字段不存在:" + column);
}
//update-end-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效
//SQL注入check
SqlInjectionUtil.filterContent(column);
@ -265,6 +274,28 @@ public class QueryGenerator {
}
}
//update-begin-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时,使用SQL注入生效
/**
* 多字段排序 判断所传字段是否存在
* @return
*/
private static boolean allColumnExist(String columnStr, Set<String> allFields){
boolean exist = true;
if(columnStr.indexOf(COMMA)>=0){
String[] arr = columnStr.split(COMMA);
for(String column: arr){
if(!allFields.contains(column)){
exist = false;
break;
}
}
}else{
exist = allFields.contains(columnStr);
}
return exist;
}
//update-end-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时,使用SQL注入生效
/**
* 高级查询
* @param queryWrapper 查询对象
@ -825,13 +856,13 @@ public class QueryGenerator {
res = field + " in "+getInConditionValue(value, isString);
break;
case LIKE:
res = field + " like "+getLikeConditionValue(value);
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
break;
case LEFT_LIKE:
res = field + " like "+getLikeConditionValue(value);
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
break;
case RIGHT_LIKE:
res = field + " like "+getLikeConditionValue(value);
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
break;
default:
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
@ -915,7 +946,14 @@ public class QueryGenerator {
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
}
private static String getLikeConditionValue(Object value) {
/**
* 先根据值判断 走左模糊还是右模糊
* 最后如果值不带任何标识(*或者%)则再根据ruleEnum判断
* @param value
* @param ruleEnum
* @return
*/
private static String getLikeConditionValue(Object value, QueryRuleEnum ruleEnum) {
String str = value.toString().trim();
if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
@ -951,11 +989,30 @@ public class QueryGenerator {
}
}
}else {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
return "N'%"+str+"%'";
}else{
return "'%"+str+"%'";
//update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
// 走到这里说明 value不带有任何模糊查询的标识(*或者%)
if (ruleEnum == QueryRuleEnum.LEFT_LIKE) {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'%" + str + "'";
} else {
return "'%" + str + "'";
}
} else if (ruleEnum == QueryRuleEnum.RIGHT_LIKE) {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'" + str + "%'";
} else {
return "'" + str + "%'";
}
} else {
if (DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())) {
return "N'%" + str + "%'";
} else {
return "'%" + str + "%'";
}
}
//update-end-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
}
}
}

2
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java

@ -54,7 +54,7 @@ public class JwtUtil {
try {
os = httpServletResponse.getOutputStream();
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setStatus(401);
httpServletResponse.setStatus(code);
os.write(new ObjectMapper().writeValueAsString(jsonResult).getBytes("UTF-8"));
os.flush();
os.close();

111
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/ResourceUtil.java

@ -0,0 +1,111 @@
package org.jeecg.common.system.util;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.annotation.EnumDict;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.util.oConvertUtils;
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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 资源加载工具类
* @Author taoYan
* @Date 2022/7/8 10:40
**/
@Slf4j
public class ResourceUtil {
/**
* 枚举字典数据
*/
private final static Map<String, List<DictModel>> enumDictData = new HashMap<>(5);
/**
* 所有java类
*/
private final static String CLASS_PATTERN="/**/*.class";
/**
* 包路径 org.jeecg
*/
private final static String BASE_PACKAGE = "org.jeecg";
/**
* 枚举类中获取字典数据的方法名
*/
private final static String METHOD_NAME = "getDictList";
/**
* 获取枚举类对应的字典数据 SysDictServiceImpl#queryAllDictItems()
* @return
*/
public static Map<String, List<DictModel>> getEnumDictData(){
if(enumDictData.keySet().size()>0){
return enumDictData;
}
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_PATTERN;
try {
Resource[] resources = resourcePatternResolver.getResources(pattern);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
for (Resource resource : resources) {
MetadataReader reader = readerFactory.getMetadataReader(resource);
String classname = reader.getClassMetadata().getClassName();
Class<?> clazz = Class.forName(classname);
EnumDict enumDict = clazz.getAnnotation(EnumDict.class);
if (enumDict != null) {
EnumDict annotation = clazz.getAnnotation(EnumDict.class);
String key = annotation.value();
if(oConvertUtils.isNotEmpty(key)){
List<DictModel> list = (List<DictModel>) clazz.getDeclaredMethod(METHOD_NAME).invoke(null);
enumDictData.put(key, list);
}
}
}
}catch (Exception e){
log.error("获取枚举类字典数据异常", e.getMessage());
// e.printStackTrace();
}
return enumDictData;
}
/**
* 用于后端字典翻译 SysDictServiceImpl#queryManyDictByKeys(java.util.List, java.util.List)
* @param dictCodeList
* @param keys
* @return
*/
public static Map<String, List<DictModel>> queryManyDictByKeys(List<String> dictCodeList, List<String> keys){
if(enumDictData.keySet().size()==0){
getEnumDictData();
}
Map<String, List<DictModel>> map = new HashMap<>();
for (String code : enumDictData.keySet()) {
if(dictCodeList.indexOf(code)>=0){
List<DictModel> dictItemList = enumDictData.get(code);
for(DictModel dm: dictItemList){
String value = dm.getValue();
if(keys.indexOf(value)>=0){
List<DictModel> list = new ArrayList<>();
list.add(new DictModel(value, dm.getText()));
map.put(code,list);
break;
}
}
}
}
return map;
}
}

11
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java

@ -2,6 +2,7 @@ package org.jeecg.common.system.vo;
import java.util.Date;
import org.jeecg.common.desensitization.annotation.SensitiveField;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat;
@ -26,21 +27,25 @@ public class LoginUser {
/**
* 登录人id
*/
@SensitiveField
private String id;
/**
* 登录人账号
*/
@SensitiveField
private String username;
/**
* 登录人名字
*/
@SensitiveField
private String realname;
/**
* 登录人密码
*/
@SensitiveField
private String password;
/**
@ -50,11 +55,13 @@ public class LoginUser {
/**
* 头像
*/
@SensitiveField
private String avatar;
/**
* 生日
*/
@SensitiveField
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@ -67,11 +74,13 @@ public class LoginUser {
/**
* 电子邮件
*/
@SensitiveField
private String email;
/**
* 电话
*/
@SensitiveField
private String phone;
/**
@ -103,11 +112,13 @@ public class LoginUser {
/**
* 职务关联职务表
*/
@SensitiveField
private String post;
/**
* 座机号
*/
@SensitiveField
private String telephone;
/**多租户id配置,编辑用户的时候设置*/

5
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/CommonUtils.java

@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.ServiceNameConstants;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.util.filter.FileTypeFilter;
import org.jeecg.common.util.oss.OssBootUtil;
@ -314,14 +315,14 @@ public class CommonUtils {
*/
public static String getBaseUrl(HttpServletRequest request) {
//1.【兼容】兼容微服务下的 base path-------
String xGatewayBasePath = request.getHeader("X_GATEWAY_BASE_PATH");
String xGatewayBasePath = request.getHeader(ServiceNameConstants.X_GATEWAY_BASE_PATH);
if(oConvertUtils.isNotEmpty(xGatewayBasePath)){
log.info("x_gateway_base_path = "+ xGatewayBasePath);
return xGatewayBasePath;
}
//2.【兼容】SSL认证之后,request.getScheme()获取不到https的问题
// https://blog.csdn.net/weixin_34376986/article/details/89767950
String scheme = request.getHeader("X-Forwarded-Scheme");
String scheme = request.getHeader(CommonConstant.X_FORWARDED_SCHEME);
if(oConvertUtils.isEmpty(scheme)){
scheme = request.getScheme();
}

2
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/RestUtil.java

@ -214,7 +214,7 @@ public class RestUtil {
}
}
// 拼接 url 参数
if (variables != null) {
if (variables != null && !variables.isEmpty()) {
url += ("?" + asUrlVariables(variables));
}
// 发送请求

108
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java

@ -4,6 +4,8 @@ import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.JeecgBootException;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Set;
import java.util.regex.Pattern;
/**
@ -20,7 +22,11 @@ public class SqlInjectionUtil {
private final static String TABLE_DICT_SIGN_SALT = "20200501";
private final static String XSS_STR = "and |exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
/**show tables*/
/**
* 正则 user() 匹配更严谨
*/
private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)";
/**正则 show tables*/
private final static String SHOW_TABLES = "show\\s+tables";
/**
@ -42,6 +48,13 @@ public class SqlInjectionUtil {
log.info(" 表字典,SQL注入漏洞签名校验成功!sign=" + sign + ",dictCode=" + dictCode);
}
/**
* sql注入过滤处理遇到注入关键字抛异常
* @param value
*/
public static void filterContent(String value) {
filterContent(value, null);
}
/**
* sql注入过滤处理遇到注入关键字抛异常
@ -49,7 +62,7 @@ public class SqlInjectionUtil {
* @param value
* @return
*/
public static void filterContent(String value) {
public static void filterContent(String value, String customXssString) {
if (value == null || "".equals(value)) {
return;
}
@ -66,19 +79,39 @@ public class SqlInjectionUtil {
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
if(Pattern.matches(SHOW_TABLES, value)){
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
if (customXssString != null) {
String[] xssArr2 = customXssString.split("\\|");
for (int i = 0; i < xssArr2.length; i++) {
if (value.indexOf(xssArr2[i]) > -1) {
log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]);
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
}
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
return;
}
/**
* sql注入过滤处理遇到注入关键字抛异常
* @param values
*/
public static void filterContent(String[] values) {
filterContent(values, null);
}
/**
* sql注入过滤处理遇到注入关键字抛异常
*
* @param values
* @return
*/
public static void filterContent(String[] values) {
public static void filterContent(String[] values, String customXssString) {
String[] xssArr = XSS_STR.split("\\|");
for (String value : values) {
if (value == null || "".equals(value)) {
@ -96,7 +129,19 @@ public class SqlInjectionUtil {
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
if(Pattern.matches(SHOW_TABLES, value)){
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
if (customXssString != null) {
String[] xssArr2 = customXssString.split("\\|");
for (int i = 0; i < xssArr2.length; i++) {
if (value.indexOf(xssArr2[i]) > -1) {
log.error("请注意,存在SQL注入关键词---> {}", xssArr2[i]);
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
}
//update-end-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
@ -111,8 +156,8 @@ public class SqlInjectionUtil {
* @return
*/
//@Deprecated
public static void specialFilterContent(String value) {
String specialXssStr = " exec | insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|";
public static void specialFilterContentForDictSql(String value) {
String specialXssStr = " exec | insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
@ -129,7 +174,7 @@ public class SqlInjectionUtil {
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
if(Pattern.matches(SHOW_TABLES, value)){
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
return;
@ -144,7 +189,7 @@ public class SqlInjectionUtil {
*/
//@Deprecated
public static void specialFilterContentForOnlineReport(String value) {
String specialXssStr = " exec | insert | delete | update | drop | chr | mid | master | truncate | char | declare |";
String specialXssStr = " exec | insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
@ -162,10 +207,53 @@ public class SqlInjectionUtil {
}
}
if(Pattern.matches(SHOW_TABLES, value)){
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
return;
}
/**
* 判断给定的字段是不是类中的属性
* @param field 字段名
* @param clazz 类对象
* @return
*/
public static boolean isClassField(String field, Class clazz){
Field[] fields = clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++){
String fieldName = fields[i].getName();
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
return true;
}
}
return false;
}
/**
* 判断给定的多个字段是不是类中的属性
* @param fieldSet 字段名set
* @param clazz 类对象
* @return
*/
public static boolean isClassField(Set<String> fieldSet, Class clazz){
Field[] fields = clazz.getDeclaredFields();
for(String field: fieldSet){
boolean exist = false;
for(int i=0;i<fields.length;i++){
String fieldName = fields[i].getName();
String tableColumnName = oConvertUtils.camelToUnderline(fieldName);
if(fieldName.equalsIgnoreCase(field) || tableColumnName.equalsIgnoreCase(field)){
exist = true;
break;
}
}
if(!exist){
return false;
}
}
return true;
}
}

14
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java

@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.constant.CacheConstant;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.desensitization.util.SensitiveInfoUtil;
import org.jeecg.common.exception.JeecgBoot401Exception;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
@ -106,9 +107,16 @@ public class TokenUtils {
public static LoginUser getLoginUser(String username, CommonAPI commonApi, RedisUtil redisUtil) {
LoginUser loginUser = null;
String loginUserKey = CacheConstant.SYS_USERS_CACHE + "::" + username;
if(redisUtil.hasKey(loginUserKey)){
loginUser = (LoginUser) redisUtil.get(loginUserKey);
}else{
//【重要】此处通过redis原生获取缓存用户,是为了解决微服务下system服务挂了,其他服务互调不通问题---
if (redisUtil.hasKey(loginUserKey)) {
try {
loginUser = (LoginUser) redisUtil.get(loginUserKey);
//解密用户
SensitiveInfoUtil.handlerObject(loginUser, false);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
// 查询用户信息
loginUser = commonApi.getUserByName(username);
}

24
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java

@ -66,22 +66,20 @@ public class AesEncryptUtil {
* @throws Exception
*/
public static String desEncrypt(String data, String key, String iv) throws Exception {
try {
byte[] encrypted1 = Base64.decode(data);
//update-begin-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
byte[] encrypted1 = Base64.decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
} catch (Exception e) {
e.printStackTrace();
return null;
}
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
//加密解码后的字符串会出现\u0000
return originalString.replaceAll("\\u0000", "");
//update-end-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
}
/**

2
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/security/AbstractQueryBlackListHandler.java

@ -168,7 +168,7 @@ public abstract class AbstractQueryBlackListHandler {
public String getError(){
// TODO
return "sql黑名单校验不通过,请联系管理员!";
return "系统设置了安全规则,敏感表和敏感字段禁止查询,联系管理员授权!";
}
}

40
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeeccgBaseConfig.java → jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeecgBaseConfig.java

@ -1,5 +1,7 @@
package org.jeecg.config;
import org.jeecg.config.vo.DomainUrl;
import org.jeecg.config.vo.Path;
import org.jeecg.config.vo.Shiro;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ -9,9 +11,15 @@ import org.springframework.stereotype.Component;
* 加载项目配置
* @author: jeecg-boot
*/
@Component("jeeccgBaseConfig")
@Component("jeecgBaseConfig")
@ConfigurationProperties(prefix = "jeecg")
public class JeeccgBaseConfig {
public class JeecgBaseConfig {
/**
* 签名密钥串(字典等敏感接口)
* @TODO 降低使用成本加的默认值,实际以 yml配置 为准
*/
private String signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a";
/**
* 是否启用安全模式
*/
@ -21,10 +29,16 @@ public class JeeccgBaseConfig {
*/
private Shiro shiro;
/**
* 签名密钥串(字典等敏感接口)
* @TODO 降低使用成本加的默认值,实际以 yml配置 为准
* 上传文件配置
*/
private String signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a";
private Path path;
/**
* 前端页面访问地址
* pc: http://localhost:3100
* app: http://localhost:8051
*/
private DomainUrl domainUrl;
public Boolean getSafeMode() {
return safeMode;
@ -49,4 +63,20 @@ public class JeeccgBaseConfig {
public void setShiro(Shiro shiro) {
this.shiro = shiro;
}
public Path getPath() {
return path;
}
public void setPath(Path path) {
this.path = path;
}
public DomainUrl getDomainUrl() {
return domainUrl;
}
public void setDomainUrl(DomainUrl domainUrl) {
this.domainUrl = domainUrl;
}
}

9
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java

@ -145,4 +145,13 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
return () -> meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "");
}
// /**
// * 注册拦截器【拦截器拦截参数,自动切换数据源——后期实现多租户切换数据源功能】
// * @param registry
// */
// @Override
// public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new DynamicDatasourceInterceptor()).addPathPatterns("/test/dynamic/**");
// }
}

15
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebSocketConfig.java

@ -1,5 +1,7 @@
package org.jeecg.config;
import org.jeecg.config.filter.WebsocketFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@ -19,4 +21,17 @@ public class WebSocketConfig {
return new ServerEndpointExporter();
}
@Bean
public WebsocketFilter websocketFilter(){
return new WebsocketFilter();
}
@Bean
public FilterRegistrationBean getFilterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(websocketFilter());
bean.addUrlPatterns("/websocket/*", "/eoaSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
return bean;
}
}

35
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/filter/RequestBodyReserveFilter.java

@ -0,0 +1,35 @@
package org.jeecg.config.filter;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.config.sign.util.BodyReaderHttpServletRequestWrapper;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 针对post请求将HttpServletRequest包一层 保留body里的参数
* @Author taoYan
* @Date 2022/4/25 19:19
**/
public class RequestBodyReserveFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(servletRequest instanceof HttpServletRequest) {
HttpServletRequest req = (HttpServletRequest) servletRequest;
// POST请求类型,才获取POST请求体
if(CommonConstant.HTTP_POST.equals(req.getMethod())){
requestWrapper = new BodyReaderHttpServletRequestWrapper(req);
}
}
if(requestWrapper == null) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
}
}

52
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/filter/WebsocketFilter.java

@ -0,0 +1,52 @@
package org.jeecg.config.filter;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.TokenUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* websocket 前端将token放到子协议里传入 与后端建立连接时需要用到http协议此处用于校验token的有效性
* @Author taoYan
* @Date 2022/4/21 17:01
**/
@Slf4j
public class WebsocketFilter implements Filter {
private static final String TOKEN_KEY = "Sec-WebSocket-Protocol";
private static CommonAPI commonApi;
private static RedisUtil redisUtil;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (commonApi == null) {
commonApi = SpringContextUtils.getBean(CommonAPI.class);
}
if (redisUtil == null) {
redisUtil = SpringContextUtils.getBean(RedisUtil.class);
}
HttpServletRequest request = (HttpServletRequest)servletRequest;
String token = request.getHeader(TOKEN_KEY);
log.info("websocket连接 Token安全校验,Path = {},token:{}", request.getRequestURI(), token);
try {
TokenUtils.verifyToken(token, commonApi, redisUtil);
} catch (Exception exception) {
log.error("websocket连接校验失败,{},token:{}", exception.getMessage(), token);
return;
}
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.setHeader(TOKEN_KEY, token);
filterChain.doFilter(servletRequest, servletResponse);
}
}

29
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java

@ -3,6 +3,9 @@ package org.jeecg.config.mybatis;
import java.util.ArrayList;
import java.util.List;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.oConvertUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
@ -71,9 +74,35 @@ public class MybatisPlusSaasConfig {
}
}));
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//update-begin-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
//update-end-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题
return interceptor;
}
/**
* 动态表名切换拦截器,用于适配vue2和vue3同一个表有多个的情况,如sys_role_index在vue3情况下表名为sys_role_index_v3
* @return
*/
private DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {
//获取需要动态解析的表名
String dynamicTableName = ThreadLocalDataHelper.get(CommonConstant.DYNAMIC_TABLE_NAME);
//当dynamicTableName不为空时才走动态表名处理逻辑,否则返回原始表名
if (ObjectUtil.isNotEmpty(dynamicTableName) && dynamicTableName.equals(tableName)) {
// 获取前端传递的版本号标识
Object version = ThreadLocalDataHelper.get(CommonConstant.VERSION);
if (ObjectUtil.isNotEmpty(version)) {
//拼接表名规则(原始表名+下划线+前端传递的版本号)
return tableName + "_" + version;
}
}
return tableName;
});
return dynamicTableNameInnerInterceptor;
}
// /**
// * 下个版本会删除,现在为了避免缓存出现问题不得不配置
// * @return

62
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/ThreadLocalDataHelper.java

@ -0,0 +1,62 @@
package org.jeecg.config.mybatis;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description: 本地线程变量存储工具类
* @author: lsq
* @date: 2022年03月25日 11:42
*/
public class ThreadLocalDataHelper {
/**
* 线程的本地变量
*/
private static final ThreadLocal<ConcurrentHashMap> REQUEST_DATA = new ThreadLocal<>();
/**
* 存储本地参数
*/
private static final ConcurrentHashMap DATA_MAP = new ConcurrentHashMap<>();
/**
* 设置请求参数
*
* @param key 参数key
* @param value 参数值
*/
public static void put(String key, Object value) {
if(ObjectUtil.isNotEmpty(value)) {
DATA_MAP.put(key, value);
REQUEST_DATA.set(DATA_MAP);
}
}
/**
* 获取请求参数值
*
* @param key 请求参数
* @return
*/
public static <T> T get(String key) {
ConcurrentHashMap dataMap = REQUEST_DATA.get();
if (CollectionUtils.isNotEmpty(dataMap)) {
return (T) dataMap.get(key);
}
return null;
}
/**
* 获取请求参数
*
* @return 请求参数 MAP 对象
*/
public static void clear() {
DATA_MAP.clear();
REQUEST_DATA.remove();
}
}

55
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/aspect/DynamicTableAspect.java

@ -0,0 +1,55 @@
package org.jeecg.config.mybatis.aspect;
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.jeecg.common.aspect.annotation.DynamicTable;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.config.mybatis.ThreadLocalDataHelper;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* 动态table切换 切面处理
*
* @author :zyf
* @date:2020-04-25
*/
@Aspect
@Component
public class DynamicTableAspect {
/**
* 定义切面拦截切入点
*/
@Pointcut("@annotation(org.jeecg.common.aspect.annotation.DynamicTable)")
public void dynamicTable() {
}
@Around("dynamicTable()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DynamicTable dynamicTable = method.getAnnotation(DynamicTable.class);
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
//获取前端传递的版本标记
String version = request.getHeader(CommonConstant.VERSION);
//存储版本号到本地线程变量
ThreadLocalDataHelper.put(CommonConstant.VERSION, version);
//存储表名到本地线程变量
ThreadLocalDataHelper.put(CommonConstant.DYNAMIC_TABLE_NAME, dynamicTable.value());
//执行方法
Object result = point.proceed();
//清空本地变量
ThreadLocalDataHelper.clear();
return result;
}
}

55
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/interceptor/DynamicDatasourceInterceptor.java

@ -0,0 +1,55 @@
package org.jeecg.config.mybatis.interceptor;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 动态数据源切换拦截器
*
* 测试拦截参数自动切换数据源
* 未来规划后面通过此机制实现多租户切换数据源功能
* @author zyf
*/
@Slf4j
public class DynamicDatasourceInterceptor implements HandlerInterceptor {
/**
* 在请求处理之前进行调用Controller方法调用之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String requestURI = request.getRequestURI();
log.info("经过多数据源Interceptor,当前路径是{}", requestURI);
//获取动态数据源名称
String dsName = request.getParameter("dsName");
String dsKey = "master";
if (StringUtils.isNotEmpty(dsName)) {
dsKey = dsName;
}
DynamicDataSourceContextHolder.push(dsKey);
return true;
}
/**
* 请求处理之后进行调用但是在视图被渲染之前Controller方法调用之后
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
/**
* 在整个请求结束之后被调用也就是在DispatcherServlet 渲染了对应的视图之后执行主要是用于进行资源清理工作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
DynamicDataSourceContextHolder.clear();
}
}

30
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java

@ -15,7 +15,7 @@ import org.crazycake.shiro.RedisClusterManager;
import org.crazycake.shiro.RedisManager;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.JeeccgBaseConfig;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
import org.jeecg.config.shiro.filters.JwtFilter;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
@ -45,11 +45,11 @@ import java.util.*;
public class ShiroConfig {
@Resource
LettuceConnectionFactory lettuceConnectionFactory;
private LettuceConnectionFactory lettuceConnectionFactory;
@Autowired
private Environment env;
@Autowired
JeeccgBaseConfig jeeccgBaseConfig;
@Resource
private JeecgBaseConfig jeecgBaseConfig;
/**
* Filter Chain定义说明
@ -64,11 +64,15 @@ public class ShiroConfig {
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
String shiroExcludeUrls = jeeccgBaseConfig.getShiro().getExcludeUrls();
if(oConvertUtils.isNotEmpty(shiroExcludeUrls)){
String[] permissionUrl = shiroExcludeUrls.split(",");
for(String url : permissionUrl){
filterChainDefinitionMap.put(url,"anon");
//支持yml方式,配置拦截排除
if(jeecgBaseConfig.getShiro()!=null){
String shiroExcludeUrls = jeecgBaseConfig.getShiro().getExcludeUrls();
if(oConvertUtils.isNotEmpty(shiroExcludeUrls)){
String[] permissionUrl = shiroExcludeUrls.split(",");
for(String url : permissionUrl){
filterChainDefinitionMap.put(url,"anon");
}
}
}
// 配置不会被拦截的链接 顺序判断
@ -126,8 +130,10 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/**/*.js.map", "anon");
filterChainDefinitionMap.put("/**/*.css.map", "anon");
//测试示例
filterChainDefinitionMap.put("/test/bigScreen/**", "anon"); //大屏模板例子
//大屏模板例子
filterChainDefinitionMap.put("/test/bigScreen/**", "anon");
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
//filterChainDefinitionMap.put("/test/jeecgDemo/rabbitMqClientTest/**", "anon"); //MQ测试
//filterChainDefinitionMap.put("/test/jeecgDemo/html", "anon"); //模板页面
//filterChainDefinitionMap.put("/test/jeecgDemo/redis/**", "anon"); //redis测试
@ -137,8 +143,6 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/newsWebsocket/**", "anon");//CMS模块
filterChainDefinitionMap.put("/vxeSocket/**", "anon");//JVxeTable无痕刷新示例
//wps
filterChainDefinitionMap.put("/v1/**","anon");
//性能监控 TODO 存在安全漏洞泄露TOEKN(durid连接池也有)
filterChainDefinitionMap.put("/actuator/**", "anon");

6
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java

@ -69,13 +69,13 @@ public class ShiroRealm extends AuthorizingRealm {
// 设置用户拥有的角色集合,比如“admin,test”
Set<String> roleSet = commonApi.queryUserRoles(username);
System.out.println(roleSet.toString());
//System.out.println(roleSet.toString());
info.setRoles(roleSet);
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
Set<String> permissionSet = commonApi.queryUserAuths(username);
info.addStringPermissions(permissionSet);
System.out.println(permissionSet);
//System.out.println(permissionSet);
log.info("===============Shiro权限认证成功==============");
return info;
}
@ -123,7 +123,7 @@ public class ShiroRealm extends AuthorizingRealm {
// 查询用户信息
log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token);
LoginUser loginUser = TokenUtils.getLoginUser(username,commonApi,redisUtil);
LoginUser loginUser = TokenUtils.getLoginUser(username, commonApi, redisUtil);
//LoginUser loginUser = commonApi.getUserByName(username);
if (loginUser == null) {
throw new AuthenticationException("用户不存在!");

14
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java

@ -107,4 +107,18 @@ public class JwtFilter extends BasicHttpAuthenticationFilter {
return super.preHandle(request, response);
}
/**
* JwtFilter中ThreadLocal需要及时清除 #3634
*
* @param request
* @param response
* @param exception
* @throws Exception
*/
@Override
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
//log.info("------清空线程中多租户的ID={}------",TenantContext.getTenant());
TenantContext.clear();
}
}

20
jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java

@ -1,5 +1,7 @@
package org.jeecg.config.sign.interceptor;
import org.jeecg.config.filter.RequestBodyReserveFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@ -24,4 +26,22 @@ public class SignAuthConfiguration implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(signAuthInterceptor()).addPathPatterns(SIGN_URL_LIST);
}
//update-begin-author:taoyan date:20220427 for: issues/I53J5E post请求X_SIGN签名拦截校验后报错, request body 为空
@Bean
public RequestBodyReserveFilter requestBodyReserveFilter(){
return new RequestBodyReserveFilter();
}
@Bean
public FilterRegistrationBean reqBodyFilterRegistrationBean(){
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(requestBodyReserveFilter());
registration.setName("requestBodyReserveFilter");
// 建议此处只添加post请求地址而不是所有的都需要走过滤器
registration.addUrlPatterns(SIGN_URL_LIST);
return registration;
}
//update-end-author:taoyan date:20220427 for: issues/I53J5E post请求X_SIGN签名拦截校验后报错, request body 为空
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save