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

This commit is contained in:
zhangdaiscott 2022-07-20 18:09:53 +08:00
parent 55201e82eb
commit 0cbdc092d1
275 changed files with 7013 additions and 40279 deletions

View File

@ -7,19 +7,19 @@
JEECG BOOT 低代码开发平台(前后端分离版本) 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) [![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/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/Blog-官方博客-blue.svg)](https://my.oschina.net/jeecg)
[![](https://img.shields.io/badge/version-3.2.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot) [![](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 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) [![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> <h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
@ -34,12 +34,21 @@ JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计松耦合、并支持任务节点灵活配置既保证了公司流程的保密性又减少了开发人员的工作量。 `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) - 微服务开发: [单体切换为微服务](http://doc.jeecg.com/2704725)
- QQ交流群 ⑥730954414、VUE3群683903138、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满) - QQ交流群 ⑥730954414、VUE3群683903138、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
> ` 提醒【QQ群是自助服务群建议给帮助您解决问题的同学发送指定红包表示感谢】 ` > ` 提醒【QQ群是自助服务群建议给帮助您解决问题的同学发送指定红包表示感谢】 `
为什么选择JEECG-BOOT? 为什么选择JEECG-BOOT?
----------------------------------- -----------------------------------
* 1.采用最新主流前后分离框架Springboot+Mybatis+antd容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发; * 1.采用最新主流前后分离框架Springboot+Mybatis+antd容易上手; 代码生成器依赖性低,灵活的扩展能力,可快速实现二次开发;
@ -340,16 +349,16 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
│ ├─Online在线表单 - 功能已开放 │ ├─Online在线表单 - 功能已开放
│ ├─Online代码生成器 - 功能已开放 │ ├─Online代码生成器 - 功能已开放
│ ├─Online在线报表 - 功能已开放 │ ├─Online在线报表 - 功能已开放
│ ├─Online在线图表(商业功能) │ ├─Online在线图表(未开源)
│ ├─Online图表模板配置(商业功能) │ ├─Online图表模板配置(未开源)
│ ├─Online布局设计(商业功能) │ ├─Online布局设计(未开源)
│ ├─多数据源管理 - 功能已开放 │ ├─多数据源管理 - 功能已开放
├─积木报表设计器(低代码) ├─积木报表设计器(低代码)
│ ├─打印设计器 │ ├─打印设计器
│ ├─数据报表设计 │ ├─数据报表设计
│ ├─图形报表设计支持echart │ ├─图形报表设计支持echart
│ ├─大屏设计器(商业功能) │ ├─大屏设计器(未开源)
│─流程模块功能 (商业功能) │─流程模块功能 (未开源)
│ ├─流程设计器 │ ├─流程设计器
│ ├─表单设计器 │ ├─表单设计器
├─大屏设计器 ├─大屏设计器
@ -364,7 +373,7 @@ Jeecg-Boot低代码开发平台可以应用在任何J2EE项目的开发中
│ └─我的抄送 │ └─我的抄送
│ └─流程委派、抄送、跳转 │ └─流程委派、抄送、跳转
│ └─。。。 │ └─。。。
│─OA办公组件 (商业功能) │─OA办公组件 (未开源)
│ ├─更多功能 │ ├─更多功能
│ └─。。。 │ └─。。。
└─其他模块 └─其他模块

View File

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

View File

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

View File

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

View File

@ -16,7 +16,9 @@ export default api
export function postAction(url,parameter) { export function postAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter); let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳添加在请求接口 Header //将签名和时间戳添加在请求接口 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({ return axios({
url: url, url: url,
@ -30,7 +32,9 @@ export function postAction(url,parameter) {
export function httpAction(url,parameter,method) { export function httpAction(url,parameter,method) {
let sign = signMd5Utils.getSign(url, parameter); let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳添加在请求接口 Header //将签名和时间戳添加在请求接口 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({ return axios({
url: url, url: url,
@ -53,7 +57,9 @@ export function putAction(url,parameter) {
export function getAction(url,parameter) { export function getAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter); let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳添加在请求接口 Header //将签名和时间戳添加在请求接口 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({ return axios({
url: url, url: url,
@ -120,13 +126,23 @@ export function saveService(parameter) {
* @param parameter * @param parameter
* @returns {*} * @returns {*}
*/ */
export function downFile(url,parameter){ export function downFile(url,parameter, method='get'){
return axios({ if(method=='get'){
url: url, return axios({
params: parameter, url: url,
method:'get' , params: parameter,
responseType: 'blob' method: method ,
}) responseType: 'blob'
})
}else{
return axios({
url: url,
method: method,
data: parameter,
responseType: 'blob'
})
}
} }
/** /**

View File

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

View File

@ -68,6 +68,9 @@
branding: false, branding: false,
menubar: false, menubar: false,
toolbar_drawer: 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) => { images_upload_handler: (blobInfo, success) => {
let formData = new FormData() let formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename()); formData.append('file', blobInfo.blob(), blobInfo.filename());

View File

@ -49,7 +49,8 @@
.jeecg-form-container-disabled .ant-upload-select{display:none} .jeecg-form-container-disabled .ant-upload-select{display:none}
.jeecg-form-container-disabled .ant-upload-list{cursor:grabbing} .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; -ms-pointer-events: auto !important;
pointer-events: auto !important; pointer-events: auto !important;
} }

View File

@ -8,7 +8,7 @@
v-on="$listeners" v-on="$listeners"
@ok="handleOk" @ok="handleOk"
@cancel="handleCancel" @cancel="handleCancel"
destroyOnClose :destroyOnClose="destroyOnClose"
> >
<slot></slot> <slot></slot>
@ -49,13 +49,17 @@
import { getClass, getStyle } from '@/utils/props-util' import { getClass, getStyle } from '@/utils/props-util'
import { triggerWindowResizeEvent } from '@/utils/util' import { triggerWindowResizeEvent } from '@/utils/util'
import ModalDragMixins from './ModalDragMixins'
export default { export default {
name: 'JModal', name: 'JModal',
mixins: [ModalDragMixins],
props: { props: {
title: String, title: String,
// 可使用 .sync 修饰符 // 可使用 .sync 修饰符
visible: Boolean, visible: Boolean,
// 是否开启拖拽
draggable: Boolean,
// 是否全屏弹窗当全屏时无论如何都会禁止 body 滚动可使用 .sync 修饰符 // 是否全屏弹窗当全屏时无论如何都会禁止 body 滚动可使用 .sync 修饰符
fullscreen: { fullscreen: {
type: Boolean, type: Boolean,
@ -71,6 +75,11 @@ export default {
type: Boolean, type: Boolean,
default: true default: true
}, },
// 关闭时销毁弹窗内容
destroyOnClose: {
type: Boolean,
default: true
},
}, },
data() { data() {
return { return {
@ -162,6 +171,16 @@ export default {
toggleFullscreen() { toggleFullscreen() {
this.innerFullscreen = !this.innerFullscreen this.innerFullscreen = !this.innerFullscreen
triggerWindowResizeEvent() 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'
}
}, },
} }

View File

@ -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]
}

View File

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

View File

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

View File

@ -763,6 +763,8 @@ export default {
console.warn(`JVxeTable.setValues必须传递数组`) console.warn(`JVxeTable.setValues必须传递数组`)
return return
} }
// 是否更新了数据
let updated = false
values.forEach((item, idx) => { values.forEach((item, idx) => {
let {rowKey, values: record} = item let {rowKey, values: record} = item
let {row} = this.getIfRowById(rowKey) let {row} = this.getIfRowById(rowKey)
@ -775,6 +777,7 @@ export default {
let oldValue = row[colKey] let oldValue = row[colKey]
let newValue = record[colKey] let newValue = record[colKey]
if (newValue !== oldValue) { if (newValue !== oldValue) {
updated = true
this.$set(row, colKey, newValue) this.$set(row, colKey, newValue)
// 触发 valueChange 事件 // 触发 valueChange 事件
this.trigger('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 */ /** 获取所有的数据包括values、deleteIds */
@ -1406,7 +1417,7 @@ const fooPatterns = [
{title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/}, {title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/},
{title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/}, {title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/},
{title: '整数', value: 'z', pattern: /^-?\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 */ /** 旧版handler转为新版Validator */

View File

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

View File

@ -1,5 +1,8 @@
import store from '@/store/' import store from '@/store/'
import { randomUUID } from '@/utils/util' import { randomUUID } from '@/utils/util'
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types'
// vxe socket // vxe socket
const vs = { const vs = {
// 页面唯一 id用于标识同一用户不同页面的websocket // 页面唯一 id用于标识同一用户不同页面的websocket
@ -52,7 +55,10 @@ const vs = {
const domain = window._CONFIG['domianURL'].replace('https://', 'wss://').replace('http://', 'ws://') const domain = window._CONFIG['domianURL'].replace('https://', 'wss://').replace('http://', 'ws://')
const url = `${domain}/vxeSocket/${userId}/${this.pageId}` 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.onopen = this.on.open.bind(this)
this.ws.onerror = this.on.error.bind(this) this.ws.onerror = this.on.error.bind(this)
this.ws.onmessage = this.on.message.bind(this) this.ws.onmessage = this.on.message.bind(this)

View File

@ -7,12 +7,15 @@
<script> <script>
import Vue from 'vue' import Vue from 'vue'
import { ACCESS_TOKEN } from "@/store/mutation-types" import { ACCESS_TOKEN } from "@/store/mutation-types"
import { TENANT_ID } from "@/store/mutation-types"
import PageLayout from '../page/PageLayout' import PageLayout from '../page/PageLayout'
import RouteView from './RouteView' import RouteView from './RouteView'
import {mixinDevice} from '@/utils/mixin'
export default { export default {
name: "IframePageContent", name: "IframePageContent",
inject:['closeCurrent'], inject:['closeCurrent'],
mixins: [mixinDevice],
data () { data () {
return { return {
url: "", url: "",
@ -47,10 +50,21 @@
} else { } else {
this.url = url 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 */ /*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(); this.closeCurrent();
window.open(this.url); window.open(this.url);
} }

View File

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

View File

@ -81,7 +81,8 @@
import ShowAnnouncement from './ShowAnnouncement' import ShowAnnouncement from './ShowAnnouncement'
import store from '@/store/' import store from '@/store/'
import DynamicNotice from './DynamicNotice' import DynamicNotice from './DynamicNotice'
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types'
export default { export default {
name: "HeaderNotice", name: "HeaderNotice",
@ -107,6 +108,8 @@
stopTimer:false, stopTimer:false,
websock: null, websock: null,
lockReconnect:false, lockReconnect:false,
//websocket错误连接次数
wsConnectErrorTime:1,
heartCheck:null, heartCheck:null,
formData:{}, formData:{},
openPath:'' openPath:''
@ -201,7 +204,10 @@
var userId = store.getters.userInfo.id; var userId = store.getters.userInfo.id;
var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://")+"/websocket/"+userId; var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://")+"/websocket/"+userId;
//console.log(url); //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.onopen = this.websocketOnopen;
this.websock.onerror = this.websocketOnerror; this.websock.onerror = this.websocketOnerror;
this.websock.onmessage = this.websocketOnmessage; this.websock.onmessage = this.websocketOnmessage;
@ -213,12 +219,21 @@
//this.heartCheck.reset().start(); //this.heartCheck.reset().start();
}, },
websocketOnerror: function (e) { 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(); this.reconnect();
}, },
websocketOnmessage: function (e) { websocketOnmessage: function (e) {
console.log("-----接收消息-------",e.data); console.log("-----接收消息-------",e.data);
var data = eval("(" + e.data + ")"); //解析对象 var data = eval("(" + e.data + ")"); //解析对象
this.voiceBroadcast(data.msgTxt)
if(data.cmd == "topic"){ if(data.cmd == "topic"){
//系统通知 //系统通知
this.loadData(); this.loadData();
@ -243,7 +258,13 @@
console.log("send failed (" + err.code + ")"); 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) { openNotification (data) {
var text = data.msgTxt; var text = data.msgTxt;
const key = `open${Date.now()}`; const key = `open${Date.now()}`;
@ -275,7 +296,7 @@
console.info("尝试重连..."); console.info("尝试重连...");
that.initWebSocket(); that.initWebSocket();
that.lockReconnect = false; that.lockReconnect = false;
}, 5000); }, 20000);
}, },
heartCheckFun(){ heartCheckFun(){
var that = this; var that = this;

View File

@ -1,3 +1,4 @@
import xss from "xss"
<template> <template>
<j-modal <j-modal
:title="title" :title="title"
@ -24,6 +25,7 @@
<script> <script>
import {getUserList} from '@/api/api' import {getUserList} from '@/api/api'
import xss from 'xss'
export default { export default {
name: "SysAnnouncementModal", name: "SysAnnouncementModal",
components: { components: {
@ -70,6 +72,11 @@
} }
//update-end---author:wangshuai ---date:20220107 for将其它页面传递过来的用户名改成用户真实姓名 //update-end---author:wangshuai ---date:20220107 for将其它页面传递过来的用户名改成用户真实姓名
this.visible = true; 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; this.record = record;
}, },
handleCancel () { handleCancel () {

View File

@ -23,7 +23,9 @@ export const WebsocketMixin = {
this.socketUrl = this.socketUrl + '/' this.socketUrl = this.socketUrl + '/'
} }
var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://") + this.socketUrl + userId + "/" + token; 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.onopen = this.websocketOnopen;
this.websock.onerror = this.websocketOnerror; this.websock.onerror = this.websocketOnerror;
this.websock.onmessage = this.websocketOnmessage; this.websock.onmessage = this.websocketOnmessage;

View File

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

View File

@ -113,6 +113,11 @@ service.interceptors.request.use(config => {
const $route = router.currentRoute const $route = router.currentRoute
if ($route && $route.name && $route.name.startsWith('low-app') && $route.params.appId) { if ($route && $route.name && $route.name.startsWith('low-app') && $route.params.appId) {
config.headers['X-Low-App-ID'] = $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 // update-end--author:sunjianlei---date:20200723---for 如果当前在low-app环境并且携带了appId就向Header里传递appId

View File

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

View File

@ -206,7 +206,7 @@ export function randomNumber() {
} }
if (arguments.length === 1) { if (arguments.length === 1) {
let [length] = arguments let [length] = arguments
// 生成指定长度的随机数字首位一定不是 0 // 生成指定长度的随机数字首位一定不是 0
let nums = [...Array(length).keys()].map((i) => (i > 0 ? random(0, 9) : random(1, 9))) let nums = [...Array(length).keys()].map((i) => (i > 0 ? random(0, 9) : random(1, 9)))
return parseInt(nums.join('')) return parseInt(nums.join(''))
} else if (arguments.length >= 2) { } else if (arguments.length >= 2) {
@ -626,4 +626,64 @@ 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';
}

View File

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

View File

@ -24,7 +24,7 @@
</a-col> </a-col>
<a-col :md="6" :sm="8"> <a-col :md="6" :sm="8">
<a-form-item label="模板类型"> <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-form-item>
</a-col> </a-col>
</template> </template>
@ -51,7 +51,7 @@
@change="handleImportExcel"> @change="handleImportExcel">
<a-button type="primary" icon="import">导入</a-button> <a-button type="primary" icon="import">导入</a-button>
</a-upload> </a-upload>
<a-dropdown v-if="selectedRowKeys.length > 0"> <!-- <a-dropdown v-if="selectedRowKeys.length > 0">
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item key="1" @click="batchDel"> <a-menu-item key="1" @click="batchDel">
<a-icon type="delete"/> <a-icon type="delete"/>
@ -61,7 +61,7 @@
<a-button style="margin-left: 8px"> 批量操作 <a-button style="margin-left: 8px"> 批量操作
<a-icon type="down"/> <a-icon type="down"/>
</a-button> </a-button>
</a-dropdown> </a-dropdown>-->
</div> </div>
<!-- table区域-begin --> <!-- table区域-begin -->
@ -91,14 +91,21 @@
<span slot="action" slot-scope="text, record"> <span slot="action" slot-scope="text, record">
<a @click="handleEdit(record)">编辑</a> <a @click="handleMyEdit(record)">编辑</a>
<a-divider type="vertical"/> <a-divider type="vertical"/>
<a-dropdown> <a-dropdown>
<a class="ant-dropdown-link">更多 <a-icon type="down"/></a> <a class="ant-dropdown-link">更多 <a-icon type="down"/></a>
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item> <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>删除</a>
</a-popconfirm> </a-popconfirm>
</a-menu-item> </a-menu-item>
@ -125,14 +132,19 @@
import SysMessageTestModal from './modules/SysMessageTestModal' import SysMessageTestModal from './modules/SysMessageTestModal'
import {JeecgListMixin} from '@/mixins/JeecgListMixin' import {JeecgListMixin} from '@/mixins/JeecgListMixin'
import JEllipsis from "@/components/jeecg/JEllipsis"; 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 { export default {
name: "SysMessageTemplateList", name: "SysMessageTemplateList",
mixins: [JeecgListMixin], mixins: [JeecgListMixin],
components: { components: {
JEllipsis, JEllipsis,
SysMessageTemplateModal, SysMessageTemplateModal,
SysMessageTestModal SysMessageTestModal,
JDictSelectTag
}, },
data() { data() {
return { return {
@ -171,16 +183,22 @@
dataIndex: 'templateType', dataIndex: 'templateType',
customRender: function (text) { customRender: function (text) {
if(text=='1') { if(text=='1') {
return "短信"; return "文本";
} }
if(text=='2') { if(text=='2') {
return "邮件"; return "富文本";
} }
if(text=='3') { }
return "微信"; },
} {
if(text=='4') { title: '是否应用',
return "系统"; align: "center",
dataIndex: 'useStatus',
customRender: function (text) {
if(text=='1') {
return "";
}else{
return '否'
} }
} }
}, },
@ -209,8 +227,60 @@
handleTest(record){ handleTest(record){
this.$refs.testModal.open(record); this.$refs.testModal.open(record);
this.$refs.testModal.title = "发送测试"; 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);
}
});
},
} }
} }
</script> </script>

View File

@ -50,6 +50,17 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </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-row class="form-row" :gutter="24">
<a-col :span="24" pull="4"> <a-col :span="24" pull="4">
<a-form-item <a-form-item
@ -85,11 +96,13 @@
import pick from 'lodash.pick' import pick from 'lodash.pick'
import { duplicateCheck } from '@/api/api' import { duplicateCheck } from '@/api/api'
import JEditor from '@/components/jeecg/JEditor' import JEditor from '@/components/jeecg/JEditor'
import JSwitch from '@/components/jeecg/JSwitch'
export default { export default {
name: "SysMessageTemplateModal", name: "SysMessageTemplateModal",
components:{ components:{
JEditor JEditor,
JSwitch
}, },
data() { data() {
return { return {
@ -111,6 +124,7 @@
templateCode: {rules: [{required: true, message: '请输入模板CODE!' },{validator: this.validateTemplateCode}]}, templateCode: {rules: [{required: true, message: '请输入模板CODE!' },{validator: this.validateTemplateCode}]},
templateName: {rules: [{required: true, message: '请输入模板标题!'}]}, templateName: {rules: [{required: true, message: '请输入模板标题!'}]},
templateContent: {rules: []}, templateContent: {rules: []},
useStatus:{rules: []},
templateType: {rules: [{required: true, message: '请输入模板类型!'}]}, templateType: {rules: [{required: true, message: '请输入模板类型!'}]},
}, },
url: { url: {
@ -140,9 +154,9 @@
this.visible = true; this.visible = true;
this.$nextTick(() => { this.$nextTick(() => {
if(this.useEditor){ 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{ }else{
this.form.setFieldsValue(pick(this.model, 'templateCode', 'templateContent', 'templateName', 'templateTestJson', 'templateType')) this.form.setFieldsValue(pick(this.model, 'useStatus', 'templateCode', 'templateContent', 'templateName', 'templateTestJson', 'templateType'))
} }
}); });
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,6 +53,9 @@
</a-radio-group> </a-radio-group>
</template> </template>
</a-form-model-item> </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 <a-form-model-item
:labelCol="labelCol" :labelCol="labelCol"
:wrapperCol="wrapperCol" :wrapperCol="wrapperCol"

View File

@ -281,6 +281,10 @@
this.$refs.modalForm.title = "编辑"; this.$refs.modalForm.title = "编辑";
this.$refs.modalForm.departDisabled = true; this.$refs.modalForm.departDisabled = true;
this.$refs.modalForm.disableSubmit = false; 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); this.$refs.modalForm.edit(record);
}, },
handleAdd: function () { handleAdd: function () {
@ -288,6 +292,10 @@
this.$message.error("请选择一个部门!") this.$message.error("请选择一个部门!")
} else { } else {
this.$refs.modalForm.departDisabled = true; 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.nextDepartOptions=[{value:this.currentDept.key,label:this.currentDept.title}]
this.$refs.modalForm.title = "新增"; this.$refs.modalForm.title = "新增";

View File

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

View File

@ -99,7 +99,7 @@
edit: "/sys/annountCement/edit", edit: "/sys/annountCement/edit",
}, },
userType:false, userType:false,
userIds:[], userIds:"",
selectedUser:[], selectedUser:[],
disabled:false, disabled:false,
msgContent:"", msgContent:"",
@ -191,16 +191,19 @@
}, },
resetUser (){ resetUser (){
this.userType = false; 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.disabled = false;
this.$refs.UserListModal.edit(null,null);
}, },
chooseMsgType(e) { chooseMsgType(e) {
if("USER" == e.target.value) { if("USER" == e.target.value) {
this.userType = true; this.userType = true;
} else { } else {
this.userType = false; 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){ startTimeValidate(rule,value,callback){

View File

@ -79,7 +79,7 @@
<script> <script>
import pick from 'lodash.pick' import pick from 'lodash.pick'
import { httpAction, postAction } from '@/api/manage' import { httpAction, postAction,getAction } from '@/api/manage'
import { validateDuplicateValue } from '@/utils/util' import { validateDuplicateValue } from '@/utils/util'
export default { export default {
@ -129,6 +129,7 @@
url: { url: {
add: '/sys/dataSource/add', add: '/sys/dataSource/add',
edit: '/sys/dataSource/edit', edit: '/sys/dataSource/edit',
queryById: '/sys/dataSource/queryById',
}, },
dbDriverMap: { dbDriverMap: {
// MySQL 数据库 // MySQL 数据库
@ -202,9 +203,17 @@
add() { add() {
this.edit({}) this.edit({})
}, },
edit(record) { async edit(record) {
this.form.resetFields() this.form.resetFields()
this.model = Object.assign({}, record) 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.visible = true
this.$nextTick(() => { this.$nextTick(() => {
this.form.setFieldsValue(pick(this.model, 'code', 'name', 'remark', 'dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword')) this.form.setFieldsValue(pick(this.model, 'code', 'name', 'remark', 'dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword'))

View File

@ -55,6 +55,11 @@
ruleClass: [{ required: true, message: '规则实现类不能为空' }], ruleClass: [{ required: true, message: '规则实现类不能为空' }],
ruleParams: [{ ruleParams: [{
validator: (rule, value, callback) => { 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 { try {
let json = JSON.parse(value) let json = JSON.parse(value)

View File

@ -15,7 +15,7 @@
</a-form-model-item> </a-form-model-item>
</a-col> </a-col>
<a-col :span="24"> <!-- <a-col :span="24">
<a-form-model-item label="开始时间" :labelCol="labelCol" :wrapperCol="wrapperCol"> <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%"/> <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> </a-form-model-item>
@ -24,7 +24,8 @@
<a-form-model-item label="结束时间" :labelCol="labelCol" :wrapperCol="wrapperCol"> <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%"/> <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-form-model-item>
</a-col> </a-col>-->
<a-col :span="24"> <a-col :span="24">
<a-form-model-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol"> <a-form-model-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group name="tenantStatus" v-model="model.status"> <a-radio-group name="tenantStatus" v-model="model.status">

View File

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

View File

@ -43,8 +43,8 @@
currdatetime: '', currdatetime: '',
loginType: 0, loginType: 0,
model:{ model:{
username: '', username: 'admin',
password: '', password: '123456',
inputCode: '' inputCode: ''
}, },
validatorRules:{ validatorRules:{
@ -136,6 +136,11 @@
this.Login(loginParams).then((res) => { this.Login(loginParams).then((res) => {
this.$emit('success', res.result) this.$emit('success', res.result)
}).catch((err) => { }).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) this.$emit('fail', err)
}); });
}else{ }else{

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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;

View File

@ -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`;

View File

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

View File

@ -536,4 +536,17 @@ public interface ISysBaseAPI extends CommonAPI {
@GetMapping("/sys/api/translateDictFromTableByKeys") @GetMapping("/sys/api/translateDictFromTableByKeys")
List<DictModel> translateDictFromTableByKeys(@RequestParam("table") String table, @RequestParam("text") String text, @RequestParam("code") String code, @RequestParam("keys") String keys); 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);
} }

View File

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

View File

@ -101,7 +101,7 @@
// log.info(" Feign request params sign: {}",sign); // log.info(" Feign request params sign: {}",sign);
// log.info("============================ [end] fegin api url ============================"); // log.info("============================ [end] fegin api url ============================");
// requestTemplate.header(CommonConstant.X_SIGN, sign); // 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) { // } catch (IOException e) {
// e.printStackTrace(); // e.printStackTrace();
// } // }
@ -146,7 +146,7 @@
// return new SpringEncoder(feignHttpMessageConverter()); // return new SpringEncoder(feignHttpMessageConverter());
// } // }
// //
// @Bean // @Bean("apiFeignDecoder")
// public Decoder feignDecoder() { // public Decoder feignDecoder() {
// return new SpringDecoder(feignHttpMessageConverter()); // return new SpringDecoder(feignHttpMessageConverter());
// } // }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}

View File

@ -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) */ /** {@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) */ /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
public static final Integer SC_OK_200 = 200; Integer SC_OK_200 = 200;
/**访问权限认证未通过 510*/ /**访问权限认证未通过 510*/
public static final Integer SC_JEECG_NO_AUTHZ=510; Integer SC_JEECG_NO_AUTHZ=510;
/** 登录用户Shiro权限缓存KEY前缀 */ /** 登录用户Shiro权限缓存KEY前缀 */
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:"; public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
/** 登录用户Token令牌缓存KEY前缀 */ /** 登录用户Token令牌缓存KEY前缀 */
public static final String PREFIX_USER_TOKEN = "prefix_user_token_"; String PREFIX_USER_TOKEN = "prefix_user_token_";
// /** Token缓存时间3600秒即一小时 */ // /** Token缓存时间3600秒即一小时 */
// public static final int TOKEN_EXPIRE_TIME = 3600; // int TOKEN_EXPIRE_TIME = 3600;
/** 登录二维码 */ /** 登录二维码 */
public static final String LOGIN_QRCODE_PRE = "QRCODELOGIN:"; String LOGIN_QRCODE_PRE = "QRCODELOGIN:";
public static final String LOGIN_QRCODE = "LQ:"; String LOGIN_QRCODE = "LQ:";
/** 登录二维码token */ /** 登录二维码token */
public static final String LOGIN_QRCODE_TOKEN = "LQT:"; String LOGIN_QRCODE_TOKEN = "LQT:";
/** /**
* 0一级菜单 * 0一级菜单
*/ */
public static final Integer MENU_TYPE_0 = 0; Integer MENU_TYPE_0 = 0;
/** /**
* 1子菜单 * 1子菜单
*/ */
public static final Integer MENU_TYPE_1 = 1; Integer MENU_TYPE_1 = 1;
/** /**
* 2按钮权限 * 2按钮权限
*/ */
public static final Integer MENU_TYPE_2 = 2; Integer MENU_TYPE_2 = 2;
/**通告对象类型USER:指定用户ALL:全体用户)*/ /**通告对象类型USER:指定用户ALL:全体用户)*/
public static final String MSG_TYPE_UESR = "USER"; String MSG_TYPE_UESR = "USER";
public static final String MSG_TYPE_ALL = "ALL"; String MSG_TYPE_ALL = "ALL";
/**发布状态0未发布1已发布2已撤销*/ /**发布状态0未发布1已发布2已撤销*/
public static final String NO_SEND = "0"; String NO_SEND = "0";
public static final String HAS_SEND = "1"; String HAS_SEND = "1";
public static final String HAS_CANCLE = "2"; String HAS_CANCLE = "2";
/**阅读状态0未读1已读*/ /**阅读状态0未读1已读*/
public static final String HAS_READ_FLAG = "1"; String HAS_READ_FLAG = "1";
public static final String NO_READ_FLAG = "0"; String NO_READ_FLAG = "0";
/**优先级L低M中H高*/ /**优先级L低M中H高*/
public static final String PRIORITY_L = "L"; String PRIORITY_L = "L";
public static final String PRIORITY_M = "M"; String PRIORITY_M = "M";
public static final String PRIORITY_H = "H"; String PRIORITY_H = "H";
/** /**
* 短信模板方式 0 .登录模板1.注册模板2.忘记密码模板 * 短信模板方式 0 .登录模板1.注册模板2.忘记密码模板
*/ */
public static final String SMS_TPL_TYPE_0 = "0"; String SMS_TPL_TYPE_0 = "0";
public static final String SMS_TPL_TYPE_1 = "1"; String SMS_TPL_TYPE_1 = "1";
public static final String SMS_TPL_TYPE_2 = "2"; String SMS_TPL_TYPE_2 = "2";
/** /**
* 状态(0无效1有效) * 状态(0无效1有效)
*/ */
public static final String STATUS_0 = "0"; String STATUS_0 = "0";
public static final String STATUS_1 = "1"; String STATUS_1 = "1";
/** /**
* 同步工作流引擎1同步0不同步 * 同步工作流引擎1同步0不同步
*/ */
public static final Integer ACT_SYNC_1 = 1; Integer ACT_SYNC_1 = 1;
public static final Integer ACT_SYNC_0 = 0; Integer ACT_SYNC_0 = 0;
/** /**
* 消息类型1:通知公告2:系统消息 * 消息类型1:通知公告2:系统消息
*/ */
public static final String MSG_CATEGORY_1 = "1"; String MSG_CATEGORY_1 = "1";
public static final String MSG_CATEGORY_2 = "2"; String MSG_CATEGORY_2 = "2";
/** /**
* 是否配置菜单的数据权限 1是0否 * 是否配置菜单的数据权限 1是0否
*/ */
public static final Integer RULE_FLAG_0 = 0; Integer RULE_FLAG_0 = 0;
public static final Integer RULE_FLAG_1 = 1; Integer RULE_FLAG_1 = 1;
/** /**
* 是否用户已被冻结 1正常(解冻) 2冻结 * 是否用户已被冻结 1正常(解冻) 2冻结
*/ */
public static final Integer USER_UNFREEZE = 1; Integer USER_UNFREEZE = 1;
public static final Integer USER_FREEZE = 2; 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授权通过 * 表单设计器URL授权通过
*/ */
public static final Integer DESIGN_FORM_URL_STATUS_PASSED = 1; Integer DESIGN_FORM_URL_STATUS_PASSED = 1;
/** /**
* 表单设计器URL授权未通过 * 表单设计器URL授权未通过
*/ */
public static final Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2; Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2;
/** /**
* 表单设计器新增 Flag * 表单设计器新增 Flag
*/ */
public static final String DESIGN_FORM_URL_TYPE_ADD = "add"; String DESIGN_FORM_URL_TYPE_ADD = "add";
/** /**
* 表单设计器修改 Flag * 表单设计器修改 Flag
*/ */
public static final String DESIGN_FORM_URL_TYPE_EDIT = "edit"; String DESIGN_FORM_URL_TYPE_EDIT = "edit";
/** /**
* 表单设计器详情 Flag * 表单设计器详情 Flag
*/ */
public static final String DESIGN_FORM_URL_TYPE_DETAIL = "detail"; String DESIGN_FORM_URL_TYPE_DETAIL = "detail";
/** /**
* 表单设计器复用数据 Flag * 表单设计器复用数据 Flag
*/ */
public static final String DESIGN_FORM_URL_TYPE_REUSE = "reuse"; String DESIGN_FORM_URL_TYPE_REUSE = "reuse";
/** /**
* 表单设计器编辑 Flag 已弃用 * 表单设计器编辑 Flag 已弃用
*/ */
public static final String DESIGN_FORM_URL_TYPE_VIEW = "view"; String DESIGN_FORM_URL_TYPE_VIEW = "view";
/** /**
* online参数值设置Y, N * online参数值设置Y, N
*/ */
public static final String ONLINE_PARAM_VAL_IS_TURE = "Y"; String ONLINE_PARAM_VAL_IS_TURE = "Y";
public static final String ONLINE_PARAM_VAL_IS_FALSE = "N"; String ONLINE_PARAM_VAL_IS_FALSE = "N";
/** /**
* 文件上传类型本地localMiniominio阿里云alioss * 文件上传类型本地localMiniominio阿里云alioss
*/ */
public static final String UPLOAD_TYPE_LOCAL = "local"; String UPLOAD_TYPE_LOCAL = "local";
public static final String UPLOAD_TYPE_MINIO = "minio"; String UPLOAD_TYPE_MINIO = "minio";
public static final String UPLOAD_TYPE_OSS = "alioss"; 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:上级 * 员工身份 1:普通员工 2:上级
*/ */
public static final Integer USER_IDENTITY_1 = 1; Integer USER_IDENTITY_1 = 1;
public static final Integer USER_IDENTITY_2 = 2; Integer USER_IDENTITY_2 = 2;
/** sys_user 表 username 唯一键索引 */ /** 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 唯一键索引 */ /** 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 唯一键索引 */ /** 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]唯一性约束 */ /** 达梦数据库升提示。违反表[SYS_USER]唯一性约束 */
public static final String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束"; String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束";
/** sys_user 表 email 唯一键索引 */ /** 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 唯一键索引 */ /** 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 唯一键索引 */ /** sys_position 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_CODE = "uniq_code"; String SQL_INDEX_UNIQ_CODE = "uniq_code";
/** sys_role 表 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 唯一键索引 */ /** 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 唯一键索引 */ /** 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消息类型 * 在线聊天 SOCKET消息类型
*/ */
public static final String IM_SOCKET_TYPE = "chatMessage"; String IM_SOCKET_TYPE = "chatMessage";
/** /**
* 在线聊天 是否开启默认添加好友 1是 0否 * 在线聊天 是否开启默认添加好友 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不同意 * 考勤补卡业务状态 1同意 2不同意
*/ */
public static final String SIGN_PATCH_BIZ_STATUS_1 = "1"; String SIGN_PATCH_BIZ_STATUS_1 = "1";
public static final String SIGN_PATCH_BIZ_STATUS_2 = "2"; 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模板还是公文发文模板) * WPS存储值类别(1 code文号 2 textWPS模板还是公文发文模板)
*/ */
public static final String WPS_TYPE_1="1"; String WPS_TYPE_1="1";
public static final String WPS_TYPE_2="2"; String WPS_TYPE_2="2";
public final static String X_ACCESS_TOKEN = "X-Access-Token"; String X_ACCESS_TOKEN = "X-Access-Token";
public final static String X_SIGN = "X-Sign"; String X_SIGN = "X-Sign";
public final static String X_TIMESTAMP = "X-TIMESTAMP"; String X_TIMESTAMP = "X-TIMESTAMP";
public final static String TOKEN_IS_INVALID_MSG = "Token失效请重新登录!"; 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 * 第三方APP同步方向本地 --> 第三方APP
@ -361,16 +362,43 @@ public interface CommonConstant {
/**String 类型的空值*/ /**String 类型的空值*/
String STRING_NULL = "null"; String STRING_NULL = "null";
/**java.util.Date 包*/ /**前端vue3版本Header参数名*/
String JAVA_UTIL_DATE = "java.util.Date";
/**.do*/
String SPOT_DO = ".do";
/**前端vue版本标识*/
String VERSION="X-Version"; String VERSION="X-Version";
/**前端vue版本*/ /**存储在线程变量里的动态表名*/
String VERSION_VUE3="vue3"; String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
/**
* http:// http协议
*/
String HTTP_PROTOCOL = "http://";
/**
* https:// https协议
*/
String HTTPS_PROTOCOL = "https://";
/** 部门表唯一keyid */
String DEPART_KEY_ID = "id";
/** 部门表唯一keyorgCode */
String DEPART_KEY_ORG_CODE = "orgCode";
/**
* 发消息 会传递一些信息到map
*/
String NOTICE_MSG_SUMMARY = "NOTICE_MSG_SUMMARY";
/**
* 发消息 会传递一个业务ID到map
*/
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
/**
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
*/
String LOGIN_TOKEN = "{LOGIN_TOKEN}";
/**
* 模板消息中 跳转地址的对应的key
*/
String MSG_HREF_URL = "url";
} }

View File

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

View File

@ -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";
}

View File

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

View File

@ -86,4 +86,34 @@ public class SymbolConstant {
* 符号 & * 符号 &
*/ */
public static final String AND = "&"; 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 = "]";
} }

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

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

View File

@ -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 "";
}

View File

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

View File

@ -16,6 +16,7 @@ import org.apache.commons.beanutils.PropertyUtils;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant; import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.SymbolConstant; 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.JeecgDataAutorUtils;
import org.jeecg.common.system.util.JwtUtil; import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.SysPermissionDataRuleModel; import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
@ -191,8 +192,8 @@ public class QueryGenerator {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
} }
// 排序逻辑 处理 // 排序逻辑 处理
doMultiFieldsOrder(queryWrapper, parameterMap); doMultiFieldsOrder(queryWrapper, parameterMap, fieldColumnMap.keySet());
//高级查询 //高级查询
doSuperQuery(queryWrapper, parameterMap, fieldColumnMap); doSuperQuery(queryWrapper, parameterMap, fieldColumnMap);
@ -228,8 +229,7 @@ public class QueryGenerator {
} }
} }
/**多字段排序 TODO 需要修改前端*/ private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap, Set<String> allFields) {
private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap) {
String column=null,order=null; String column=null,order=null;
if(parameterMap!=null&& parameterMap.containsKey(ORDER_COLUMN)) { if(parameterMap!=null&& parameterMap.containsKey(ORDER_COLUMN)) {
column = parameterMap.get(ORDER_COLUMN)[0]; column = parameterMap.get(ORDER_COLUMN)[0];
@ -243,6 +243,15 @@ public class QueryGenerator {
if(column.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) { if(column.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
column = column.substring(0, column.lastIndexOf(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 //SQL注入check
SqlInjectionUtil.filterContent(column); SqlInjectionUtil.filterContent(column);
@ -264,6 +273,28 @@ public class QueryGenerator {
//update-end--Author:scott Date:20210531 for36 多条件排序无效问题修正------- //update-end--Author:scott Date:20210531 for36 多条件排序无效问题修正-------
} }
} }
//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注入生效
/** /**
* 高级查询 * 高级查询
@ -825,13 +856,13 @@ public class QueryGenerator {
res = field + " in "+getInConditionValue(value, isString); res = field + " in "+getInConditionValue(value, isString);
break; break;
case LIKE: case LIKE:
res = field + " like "+getLikeConditionValue(value); res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
break; break;
case LEFT_LIKE: case LEFT_LIKE:
res = field + " like "+getLikeConditionValue(value); res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
break; break;
case RIGHT_LIKE: case RIGHT_LIKE:
res = field + " like "+getLikeConditionValue(value); res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
break; break;
default: default:
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType); res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
@ -914,8 +945,15 @@ public class QueryGenerator {
} }
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错 //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(); String str = value.toString().trim();
if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) { if(str.startsWith(SymbolConstant.ASTERISK) && str.endsWith(SymbolConstant.ASTERISK)) {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){ if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
@ -951,11 +989,30 @@ public class QueryGenerator {
} }
} }
}else { }else {
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
return "N'%"+str+"%'"; //update-begin-author:taoyan date:2022-6-30 for: issues/3810 数据权限规则问题
}else{ // 走到这里说明 value不带有任何模糊查询的标识(*或者%)
return "'%"+str+"%'"; 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 数据权限规则问题
} }
} }
} }

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.exception.JeecgBootException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Set;
import java.util.regex.Pattern; 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 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()"; 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"; private final static String SHOW_TABLES = "show\\s+tables";
/** /**
@ -42,6 +48,13 @@ public class SqlInjectionUtil {
log.info(" 表字典SQL注入漏洞签名校验成功sign=" + sign + ",dictCode=" + dictCode); log.info(" 表字典SQL注入漏洞签名校验成功sign=" + sign + ",dictCode=" + dictCode);
} }
/**
* sql注入过滤处理遇到注入关键字抛异常
* @param value
*/
public static void filterContent(String value) {
filterContent(value, null);
}
/** /**
* sql注入过滤处理遇到注入关键字抛异常 * sql注入过滤处理遇到注入关键字抛异常
@ -49,7 +62,7 @@ public class SqlInjectionUtil {
* @param value * @param value
* @return * @return
*/ */
public static void filterContent(String value) { public static void filterContent(String value, String customXssString) {
if (value == null || "".equals(value)) { if (value == null || "".equals(value)) {
return; return;
} }
@ -66,19 +79,39 @@ public class SqlInjectionUtil {
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); 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); throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
} }
return; return;
} }
/**
* sql注入过滤处理遇到注入关键字抛异常
* @param values
*/
public static void filterContent(String[] values) {
filterContent(values, null);
}
/** /**
* sql注入过滤处理遇到注入关键字抛异常 * sql注入过滤处理遇到注入关键字抛异常
* *
* @param values * @param values
* @return * @return
*/ */
public static void filterContent(String[] values) { public static void filterContent(String[] values, String customXssString) {
String[] xssArr = XSS_STR.split("\\|"); String[] xssArr = XSS_STR.split("\\|");
for (String value : values) { for (String value : values) {
if (value == null || "".equals(value)) { if (value == null || "".equals(value)) {
@ -96,7 +129,19 @@ public class SqlInjectionUtil {
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); 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); throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
} }
} }
@ -111,8 +156,8 @@ public class SqlInjectionUtil {
* @return * @return
*/ */
//@Deprecated //@Deprecated
public static void specialFilterContent(String value) { public static void specialFilterContentForDictSql(String value) {
String specialXssStr = " exec | insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|"; String specialXssStr = " exec | insert | select | delete | update | drop | count | chr | mid | master | truncate | char | declare |;|+|user()";
String[] xssArr = specialXssStr.split("\\|"); String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) { if (value == null || "".equals(value)) {
return; return;
@ -129,7 +174,7 @@ public class SqlInjectionUtil {
throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value); 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); throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
} }
return; return;
@ -144,7 +189,7 @@ public class SqlInjectionUtil {
*/ */
//@Deprecated //@Deprecated
public static void specialFilterContentForOnlineReport(String value) { 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("\\|"); String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) { if (value == null || "".equals(value)) {
return; 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); throw new RuntimeException("请注意值可能存在SQL注入风险!--->" + value);
} }
return; 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;
}
} }

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
package org.jeecg.config; package org.jeecg.config;
import org.jeecg.config.vo.DomainUrl;
import org.jeecg.config.vo.Path;
import org.jeecg.config.vo.Shiro; import org.jeecg.config.vo.Shiro;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -9,9 +11,15 @@ import org.springframework.stereotype.Component;
* 加载项目配置 * 加载项目配置
* @author: jeecg-boot * @author: jeecg-boot
*/ */
@Component("jeeccgBaseConfig") @Component("jeecgBaseConfig")
@ConfigurationProperties(prefix = "jeecg") @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; 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() { public Boolean getSafeMode() {
return safeMode; return safeMode;
@ -49,4 +63,20 @@ public class JeeccgBaseConfig {
public void setShiro(Shiro shiro) { public void setShiro(Shiro shiro) {
this.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;
}
} }

View File

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

View File

@ -1,5 +1,7 @@
package org.jeecg.config; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter; import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@ -19,4 +21,17 @@ public class WebSocketConfig {
return new ServerEndpointExporter(); 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;
}
} }

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -3,6 +3,9 @@ package org.jeecg.config.mybatis;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.jeecg.common.util.oConvertUtils;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -71,9 +74,35 @@ public class MybatisPlusSaasConfig {
} }
})); }));
interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 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; 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 // * @return

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

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

View File

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

View File

@ -107,4 +107,18 @@ public class JwtFilter extends BasicHttpAuthenticationFilter {
return super.preHandle(request, response); 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();
}
} }

View File

@ -1,5 +1,7 @@
package org.jeecg.config.sign.interceptor; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@ -24,4 +26,22 @@ public class SignAuthConfiguration implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(signAuthInterceptor()).addPathPatterns(SIGN_URL_LIST); 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