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

pull/3903/head
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-242【online表单 高级查询】开关组件设置扩展参数为[0,1] 时高级查询选择后查询仍然是Y/N
item['extendOption'] = node.dataRef.extendOption || ['Y', 'N']
//update-begin-author:taoyan for: VUEN-242【online表单 高级查询】开关组件设置扩展参数为[0,1] 时高级查询选择后查询仍然是Y/N
item['customReturnField'] = customReturnField 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-329【bug】为什么不是失去焦点的时候触发手机号校验
callback('') if(!value && value!=='0'){
} else {
callback() callback()
}else{
if (!reg.test(value)) {
callback('')
} else {
callback()
}
} }
//update-end-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候触发手机号校验
} }
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) {
@ -627,3 +627,63 @@ export function aspectAroundFunction(obj, funcName, callback) {
}) })
} }
} }
/**
*
* @param ms
* @return {Promise<unknown>}
*/
export function sleep(ms) {
return new Promise(function (resolve) {
return setTimeout(resolve, ms);
});
}
/**
* $refs
* $refs
* $refs
*
* let modalRef = getRefPromise(this, 'modal')
* @param vm vue
* @param name ref
* @param noComment $el
**/
export function getRefPromise(vm, name, noComment = true) {
return new Promise((resolve) => {
(function next() {
let ref = vm.$refs[name]
if (ref && (noComment && ref.$el.tagName)) {
resolve(ref)
} else {
setTimeout(() => {
if (noComment) {
vm.$forceUpdate()
}
next()
}, 10)
}
})()
})
}
/**
* xlsxmime-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";
/**
* nullnull
* @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,6 +132,10 @@
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",
@ -132,7 +143,8 @@
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,7 +227,59 @@
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);
}
});
},
} }
} }

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-329【bug】为什么不是失去焦点的时候触发手机号校验
this.$nextTick(()=>{
this.$refs.form.validateField('mobile')
})
//update-end-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候触发手机号校验
}, },
// 触发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-329【bug】为什么不是失去焦点的时候触发手机号校验
this.$nextTick(()=>{
this.$refs.form.validateField('mobile')
})
//update-end-author:taoyan date:20220316 for: VUEN-329【bug】为什么不是失去焦点的时候触发手机号校验
}, },
// 触发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%;">
@ -54,10 +54,10 @@
<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";
/** /**
* (01) * (01)
*/ */
public static final String STATUS_0 = "0"; String STATUS_0 = "0";
public static final String STATUS_1 = "1"; String STATUS_1 = "1";
/** /**
* 10 * 10
*/ */
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";
/** /**
* 10 * 10
*/ */
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";
/** /**
* onlineY, N * onlineY, 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";
/** /**
* localMiniominioalioss * localMiniominioalioss
*/ */
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";
/**
* IDmap
*/
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: 20220425 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,
/** /**
* OnlineOnline * OnlineOnline

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);
}
/**
* [] 1815
* @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;
@ -192,7 +193,7 @@ public class QueryGenerator {
} }
} }
// 排序逻辑 处理 // 排序逻辑 处理
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);
@ -265,6 +274,28 @@ public class QueryGenerator {
} }
} }
//update-begin-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时使用SQL注入生效
/**
*
* @return
*/
private static boolean allColumnExist(String columnStr, Set<String> allFields){
boolean exist = true;
if(columnStr.indexOf(COMMA)>=0){
String[] arr = columnStr.split(COMMA);
for(String column: arr){
if(!allFields.contains(column)){
exist = false;
break;
}
}
}else{
exist = allFields.contains(columnStr);
}
return exist;
}
//update-end-author:taoyan date:2022-5-23 for: issues/3676 获取系统用户列表时使用SQL注入生效
/** /**
* *
* @param queryWrapper * @param queryWrapper
@ -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);
@ -915,7 +946,14 @@ 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 【vue3】online表单测试发现的新问题 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 【vue3】online表单测试发现的新问题 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;
/**
* postHttpServletRequest 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 httptoken
* @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;
} }
/**
* ,vue2vue3,sys_role_indexvue3sys_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: 20220325 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");
}
} }
} }
// 配置不会被拦截的链接 顺序判断 // 配置不会被拦截的链接 顺序判断
@ -126,8 +130,10 @@ public class ShiroConfig {
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);
} }
/**
* JwtFilterThreadLocal #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