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

pull/3903/head
zhangdaiscott 2 years ago
parent 55201e82eb
commit 0cbdc092d1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -538,6 +538,7 @@
superQuery: {
fieldList: [
{ type: 'input', value: 'name', text: '', },
{ type: 'switch', value: 'switch', text: '', },
{ type: 'select', value: 'sex', text: '', dictCode: 'sex' },
{ type: 'number', value: 'age', text: '', },
{

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

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

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

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

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

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

@ -115,7 +115,7 @@
align:"center",
dataIndex: 'id'
},
{
/*{
title:'',
align:"center",
dataIndex: 'beginDate'
@ -124,7 +124,7 @@
title:'',
align:"center",
dataIndex: 'endDate'
},
},*/
{
title:'',
align:"center",

@ -156,10 +156,6 @@
</a-popconfirm>
</a-menu-item>
<a-menu-item>
<a href="javascript:;" @click="handleAgentSettings(record.username)"></a>
</a-menu-item>
</a-menu>
</a-dropdown>
</span>
@ -378,10 +374,6 @@
handleChangePassword(username) {
this.$refs.passwordmodal.show(username);
},
handleAgentSettings(username){
this.$refs.sysUserAgentModal.agentSettings(username);
this.$refs.sysUserAgentModal.title = "用户代理人设置";
},
passwordModalOk() {
//TODO 密码修改完成 不需要刷新页面可以把datasource中的数据更新一下
},

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

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

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

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

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

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

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

@ -82,7 +82,7 @@
<a-radio :value="2"></a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="负责部门" :labelCol="labelCol" :wrapperCol="wrapperCol" v-if="departIdShow==true">
<a-form-model-item label="负责部门" :labelCol="labelCol" :wrapperCol="wrapperCol" v-show="departIdShow==true">
<j-multi-select-tag
:disabled="disableSubmit"
v-model="model.departIds"

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

@ -1,7 +1,7 @@
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

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

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

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

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

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

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

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

@ -338,4 +338,17 @@ public interface ISysBaseAPI extends CommonAPI {
*/
List<DictModel> loadDictItemByKeyword(String dictCode, String keyword, Integer pageSize);
/**
*
* @param message
*/
void sendTemplateMessage(MessageDTO message);
/**
*
* @param templateCode
* @return
*/
String getTemplateContent(String templateCode);
}

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

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

@ -4,6 +4,7 @@ import lombok.Data;
import org.jeecg.common.constant.CommonConstant;
import java.io.Serializable;
import java.util.Map;
/**
*
@ -72,4 +73,18 @@ public class MessageDTO implements Serializable {
this.category = category;
}
/**
*
*/
protected String templateCode;
/**
* org.jeecg.common.constant.enums.MessageTypeEnum
*/
protected String type;
/**
*
*/
protected Map<String, Object> data;
}

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

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

@ -9,320 +9,321 @@ public interface CommonConstant {
/**
*
*/
public static final Integer STATUS_NORMAL = 0;
Integer STATUS_NORMAL = 0;
/**
*
*/
public static final Integer STATUS_DISABLE = -1;
Integer STATUS_DISABLE = -1;
/**
*
*/
public static final Integer DEL_FLAG_1 = 1;
Integer DEL_FLAG_1 = 1;
/**
*
*/
public static final Integer DEL_FLAG_0 = 0;
Integer DEL_FLAG_0 = 0;
/**
*
*/
public static final int LOG_TYPE_1 = 1;
int LOG_TYPE_1 = 1;
/**
*
*/
public static final int LOG_TYPE_2 = 2;
int LOG_TYPE_2 = 2;
/**
*
*/
public static final int OPERATE_TYPE_1 = 1;
int OPERATE_TYPE_1 = 1;
/**
*
*/
public static final int OPERATE_TYPE_2 = 2;
int OPERATE_TYPE_2 = 2;
/**
*
*/
public static final int OPERATE_TYPE_3 = 3;
int OPERATE_TYPE_3 = 3;
/**
*
*/
public static final int OPERATE_TYPE_4 = 4;
int OPERATE_TYPE_4 = 4;
/**
*
*/
public static final int OPERATE_TYPE_5 = 5;
int OPERATE_TYPE_5 = 5;
/**
*
*/
public static final int OPERATE_TYPE_6 = 6;
int OPERATE_TYPE_6 = 6;
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
public static final Integer SC_OK_200 = 200;
Integer SC_OK_200 = 200;
/**访问权限认证未通过 510*/
public static final Integer SC_JEECG_NO_AUTHZ=510;
Integer SC_JEECG_NO_AUTHZ=510;
/** 登录用户Shiro权限缓存KEY前缀 */
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
/** 登录用户Token令牌缓存KEY前缀 */
public static final String PREFIX_USER_TOKEN = "prefix_user_token_";
String PREFIX_USER_TOKEN = "prefix_user_token_";
// /** Token缓存时间3600秒即一小时 */
// public static final int TOKEN_EXPIRE_TIME = 3600;
// int TOKEN_EXPIRE_TIME = 3600;
/** 登录二维码 */
public static final String LOGIN_QRCODE_PRE = "QRCODELOGIN:";
public static final String LOGIN_QRCODE = "LQ:";
String LOGIN_QRCODE_PRE = "QRCODELOGIN:";
String LOGIN_QRCODE = "LQ:";
/** 登录二维码token */
public static final String LOGIN_QRCODE_TOKEN = "LQT:";
String LOGIN_QRCODE_TOKEN = "LQT:";
/**
* 0
*/
public static final Integer MENU_TYPE_0 = 0;
Integer MENU_TYPE_0 = 0;
/**
* 1
*/
public static final Integer MENU_TYPE_1 = 1;
Integer MENU_TYPE_1 = 1;
/**
* 2
*/
public static final Integer MENU_TYPE_2 = 2;
Integer MENU_TYPE_2 = 2;
/**通告对象类型USER:指定用户ALL:全体用户)*/
public static final String MSG_TYPE_UESR = "USER";
public static final String MSG_TYPE_ALL = "ALL";
String MSG_TYPE_UESR = "USER";
String MSG_TYPE_ALL = "ALL";
/**发布状态0未发布1已发布2已撤销*/
public static final String NO_SEND = "0";
public static final String HAS_SEND = "1";
public static final String HAS_CANCLE = "2";
String NO_SEND = "0";
String HAS_SEND = "1";
String HAS_CANCLE = "2";
/**阅读状态0未读1已读*/
public static final String HAS_READ_FLAG = "1";
public static final String NO_READ_FLAG = "0";
String HAS_READ_FLAG = "1";
String NO_READ_FLAG = "0";
/**优先级L低M中H高*/
public static final String PRIORITY_L = "L";
public static final String PRIORITY_M = "M";
public static final String PRIORITY_H = "H";
String PRIORITY_L = "L";
String PRIORITY_M = "M";
String PRIORITY_H = "H";
/**
* 0 .1.2.
*/
public static final String SMS_TPL_TYPE_0 = "0";
public static final String SMS_TPL_TYPE_1 = "1";
public static final String SMS_TPL_TYPE_2 = "2";
String SMS_TPL_TYPE_0 = "0";
String SMS_TPL_TYPE_1 = "1";
String SMS_TPL_TYPE_2 = "2";
/**
* (01)
*/
public static final String STATUS_0 = "0";
public static final String STATUS_1 = "1";
String STATUS_0 = "0";
String STATUS_1 = "1";
/**
* 10
*/
public static final Integer ACT_SYNC_1 = 1;
public static final Integer ACT_SYNC_0 = 0;
Integer ACT_SYNC_1 = 1;
Integer ACT_SYNC_0 = 0;
/**
* 1:2:
*/
public static final String MSG_CATEGORY_1 = "1";
public static final String MSG_CATEGORY_2 = "2";
String MSG_CATEGORY_1 = "1";
String MSG_CATEGORY_2 = "2";
/**
* 10
*/
public static final Integer RULE_FLAG_0 = 0;
public static final Integer RULE_FLAG_1 = 1;
Integer RULE_FLAG_0 = 0;
Integer RULE_FLAG_1 = 1;
/**
* 1() 2
*/
public static final Integer USER_UNFREEZE = 1;
public static final Integer USER_FREEZE = 2;
Integer USER_UNFREEZE = 1;
Integer USER_FREEZE = 2;
/**字典翻译文本后缀*/
public static final String DICT_TEXT_SUFFIX = "_dictText";
String DICT_TEXT_SUFFIX = "_dictText";
/**
*
*/
public static final Integer DESIGN_FORM_TYPE_MAIN = 1;
Integer DESIGN_FORM_TYPE_MAIN = 1;
/**
*
*/
public static final Integer DESIGN_FORM_TYPE_SUB = 2;
Integer DESIGN_FORM_TYPE_SUB = 2;
/**
* URL
*/
public static final Integer DESIGN_FORM_URL_STATUS_PASSED = 1;
Integer DESIGN_FORM_URL_STATUS_PASSED = 1;
/**
* URL
*/
public static final Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2;
Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2;
/**
* Flag
*/
public static final String DESIGN_FORM_URL_TYPE_ADD = "add";
String DESIGN_FORM_URL_TYPE_ADD = "add";
/**
* Flag
*/
public static final String DESIGN_FORM_URL_TYPE_EDIT = "edit";
String DESIGN_FORM_URL_TYPE_EDIT = "edit";
/**
* Flag
*/
public static final String DESIGN_FORM_URL_TYPE_DETAIL = "detail";
String DESIGN_FORM_URL_TYPE_DETAIL = "detail";
/**
* Flag
*/
public static final String DESIGN_FORM_URL_TYPE_REUSE = "reuse";
String DESIGN_FORM_URL_TYPE_REUSE = "reuse";
/**
* Flag
*/
public static final String DESIGN_FORM_URL_TYPE_VIEW = "view";
String DESIGN_FORM_URL_TYPE_VIEW = "view";
/**
* onlineY, N
*/
public static final String ONLINE_PARAM_VAL_IS_TURE = "Y";
public static final String ONLINE_PARAM_VAL_IS_FALSE = "N";
String ONLINE_PARAM_VAL_IS_TURE = "Y";
String ONLINE_PARAM_VAL_IS_FALSE = "N";
/**
* localMiniominioalioss
*/
public static final String UPLOAD_TYPE_LOCAL = "local";
public static final String UPLOAD_TYPE_MINIO = "minio";
public static final String UPLOAD_TYPE_OSS = "alioss";
String UPLOAD_TYPE_LOCAL = "local";
String UPLOAD_TYPE_MINIO = "minio";
String UPLOAD_TYPE_OSS = "alioss";
/**
*
*/
public static final String UPLOAD_CUSTOM_BUCKET = "eoafile";
String UPLOAD_CUSTOM_BUCKET = "eoafile";
/**
*
*/
public static final String UPLOAD_CUSTOM_PATH = "eoafile";
String UPLOAD_CUSTOM_PATH = "eoafile";
/**
*
*/
public static final Integer UPLOAD_EFFECTIVE_DAYS = 1;
Integer UPLOAD_EFFECTIVE_DAYS = 1;
/**
* 1: 2:
*/
public static final Integer USER_IDENTITY_1 = 1;
public static final Integer USER_IDENTITY_2 = 2;
Integer USER_IDENTITY_1 = 1;
Integer USER_IDENTITY_2 = 2;
/** sys_user 表 username 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_USER_USERNAME = "uniq_sys_user_username";
String SQL_INDEX_UNIQ_SYS_USER_USERNAME = "uniq_sys_user_username";
/** sys_user 表 work_no 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_USER_WORK_NO = "uniq_sys_user_work_no";
String SQL_INDEX_UNIQ_SYS_USER_WORK_NO = "uniq_sys_user_work_no";
/** sys_user 表 phone 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_USER_PHONE = "uniq_sys_user_phone";
String SQL_INDEX_UNIQ_SYS_USER_PHONE = "uniq_sys_user_phone";
/** 达梦数据库升提示。违反表[SYS_USER]唯一性约束 */
public static final String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束";
String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束";
/** sys_user 表 email 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_USER_EMAIL = "uniq_sys_user_email";
String SQL_INDEX_UNIQ_SYS_USER_EMAIL = "uniq_sys_user_email";
/** sys_quartz_job 表 job_class_name 唯一键索引 */
public static final String SQL_INDEX_UNIQ_JOB_CLASS_NAME = "uniq_job_class_name";
String SQL_INDEX_UNIQ_JOB_CLASS_NAME = "uniq_job_class_name";
/** sys_position 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_CODE = "uniq_code";
String SQL_INDEX_UNIQ_CODE = "uniq_code";
/** sys_role 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_SYS_ROLE_CODE = "uniq_sys_role_role_code";
String SQL_INDEX_UNIQ_SYS_ROLE_CODE = "uniq_sys_role_role_code";
/** sys_depart 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_DEPART_ORG_CODE = "uniq_depart_org_code";
String SQL_INDEX_UNIQ_DEPART_ORG_CODE = "uniq_depart_org_code";
/** sys_category 表 code 唯一键索引 */
public static final String SQL_INDEX_UNIQ_CATEGORY_CODE = "idx_sc_code";
String SQL_INDEX_UNIQ_CATEGORY_CODE = "idx_sc_code";
/**
* 线
*/
public static final String IM_DEFAULT_GROUP = "1";
String IM_DEFAULT_GROUP = "1";
/**
* 线
*/
public static final String IM_UPLOAD_CUSTOM_PATH = "imfile";
String IM_UPLOAD_CUSTOM_PATH = "imfile";
/**
* 线
*/
public static final String IM_STATUS_ONLINE = "online";
String IM_STATUS_ONLINE = "online";
/**
* 线 SOCKET
*/
public static final String IM_SOCKET_TYPE = "chatMessage";
String IM_SOCKET_TYPE = "chatMessage";
/**
* 线 1 0
*/
public static final String IM_DEFAULT_ADD_FRIEND = "1";
String IM_DEFAULT_ADD_FRIEND = "1";
/**
* 线
*/
public static final String IM_PREFIX_USER_FRIEND_CACHE = "sys:cache:im:im_prefix_user_friend_";
String IM_PREFIX_USER_FRIEND_CACHE = "sys:cache:im:im_prefix_user_friend_";
/**
* 1 2
*/
public static final String SIGN_PATCH_BIZ_STATUS_1 = "1";
public static final String SIGN_PATCH_BIZ_STATUS_2 = "2";
String SIGN_PATCH_BIZ_STATUS_1 = "1";
String SIGN_PATCH_BIZ_STATUS_2 = "2";
/**
*
*/
public static final String UPLOAD_CUSTOM_PATH_OFFICIAL = "officialdoc";
String UPLOAD_CUSTOM_PATH_OFFICIAL = "officialdoc";
/**
*
*/
public static final String DOWNLOAD_CUSTOM_PATH_OFFICIAL = "officaldown";
String DOWNLOAD_CUSTOM_PATH_OFFICIAL = "officaldown";
/**
* WPS(1 code 2 textWPS)
*/
public static final String WPS_TYPE_1="1";
public static final String WPS_TYPE_2="2";
String WPS_TYPE_1="1";
String WPS_TYPE_2="2";
public final static String X_ACCESS_TOKEN = "X-Access-Token";
public final static String X_SIGN = "X-Sign";
public final static String X_TIMESTAMP = "X-TIMESTAMP";
public final static String TOKEN_IS_INVALID_MSG = "Token失效请重新登录!";
String X_ACCESS_TOKEN = "X-Access-Token";
String X_SIGN = "X-Sign";
String X_TIMESTAMP = "X-TIMESTAMP";
String TOKEN_IS_INVALID_MSG = "Token失效请重新登录!";
String X_FORWARDED_SCHEME = "X-Forwarded-Scheme";
/**
*
*/
public final static String TENANT_ID = "tenant-id";
String TENANT_ID = "tenant-id";
/**
*
*/
public final static String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr";
String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr";
/**
* /
*/
public final static String THIRD_LOGIN_CODE = "third_login_code";
String THIRD_LOGIN_CODE = "third_login_code";
/**
* APP --> APP
@ -361,16 +362,43 @@ public interface CommonConstant {
/**String 类型的空值*/
String STRING_NULL = "null";
/**java.util.Date 包*/
String JAVA_UTIL_DATE = "java.util.Date";
/**前端vue3版本Header参数名*/
String VERSION="X-Version";
/**.do*/
String SPOT_DO = ".do";
/**存储在线程变量里的动态表名*/
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
/**
* http:// http协议
*/
String HTTP_PROTOCOL = "http://";
/**
* https:// https协议
*/
String HTTPS_PROTOCOL = "https://";
/**前端vue版本标识*/
String VERSION="X-Version";
/** 部门表唯一keyid */
String DEPART_KEY_ID = "id";
/** 部门表唯一keyorgCode */
String DEPART_KEY_ORG_CODE = "orgCode";
/**
* map
*/
String NOTICE_MSG_SUMMARY = "NOTICE_MSG_SUMMARY";
/**前端vue版本*/
String VERSION_VUE3="vue3";
/**
* IDmap
*/
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
/**
* token,token
*/
String LOGIN_TOKEN = "{LOGIN_TOKEN}";
/**
* key
*/
String MSG_HREF_URL = "url";
}

@ -153,4 +153,14 @@ public interface DataBaseConstant {
* sql where
*/
String SQL_WHERE = "where";
/**
* sql asc
*/
String SQL_ASC = "asc";
/**
* sqlserver,
*/
String DB_TYPE_SQL_SERVER_BLANK = "sql server";
}

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

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

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

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

@ -15,6 +15,8 @@ public enum LowAppAopEnum {
*
*/
DELETE,
/** 复制表单操作 */
COPY,
/**
* OnlineOnline

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -2,6 +2,7 @@ package org.jeecg.common.system.vo;
import java.util.Date;
import org.jeecg.common.desensitization.annotation.SensitiveField;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat;
@ -26,21 +27,25 @@ public class LoginUser {
/**
* id
*/
@SensitiveField
private String id;
/**
*
*/
@SensitiveField
private String username;
/**
*
*/
@SensitiveField
private String realname;
/**
*
*/
@SensitiveField
private String password;
/**
@ -50,11 +55,13 @@ public class LoginUser {
/**
*
*/
@SensitiveField
private String avatar;
/**
*
*/
@SensitiveField
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@ -67,11 +74,13 @@ public class LoginUser {
/**
*
*/
@SensitiveField
private String email;
/**
*
*/
@SensitiveField
private String phone;
/**
@ -103,11 +112,13 @@ public class LoginUser {
/**
*
*/
@SensitiveField
private String post;
/**
*
*/
@SensitiveField
private String telephone;
/**多租户id配置编辑用户的时候设置*/

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

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

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

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

@ -66,7 +66,7 @@ public class AesEncryptUtil {
* @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);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
@ -77,11 +77,9 @@ public class AesEncryptUtil {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
} catch (Exception e) {
e.printStackTrace();
return null;
}
//加密解码后的字符串会出现\u0000
return originalString.replaceAll("\\u0000", "");
//update-end-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
}
/**

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

@ -1,5 +1,7 @@
package org.jeecg.config;
import org.jeecg.config.vo.DomainUrl;
import org.jeecg.config.vo.Path;
import org.jeecg.config.vo.Shiro;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ -9,9 +11,15 @@ import org.springframework.stereotype.Component;
*
* @author: jeecg-boot
*/
@Component("jeeccgBaseConfig")
@Component("jeecgBaseConfig")
@ConfigurationProperties(prefix = "jeecg")
public class JeeccgBaseConfig {
public class JeecgBaseConfig {
/**
* ()
* @TODO 使, yml
*/
private String signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a";
/**
*
*/
@ -21,10 +29,16 @@ public class JeeccgBaseConfig {
*/
private Shiro shiro;
/**
* ()
* @TODO 使, yml
*
*/
private String signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a";
private Path path;
/**
* 访
* pc: http://localhost:3100
* app: http://localhost:8051
*/
private DomainUrl domainUrl;
public Boolean getSafeMode() {
return safeMode;
@ -49,4 +63,20 @@ public class JeeccgBaseConfig {
public void setShiro(Shiro shiro) {
this.shiro = shiro;
}
public Path getPath() {
return path;
}
public void setPath(Path path) {
this.path = path;
}
public DomainUrl getDomainUrl() {
return domainUrl;
}
public void setDomainUrl(DomainUrl domainUrl) {
this.domainUrl = domainUrl;
}
}

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

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

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

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

@ -3,6 +3,9 @@ package org.jeecg.config.mybatis;
import java.util.ArrayList;
import java.util.List;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.oConvertUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
@ -71,9 +74,35 @@ public class MybatisPlusSaasConfig {
}
}));
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//update-begin-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
//update-end-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题
return interceptor;
}
/**
* ,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

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

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

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

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

@ -69,13 +69,13 @@ public class ShiroRealm extends AuthorizingRealm {
// 设置用户拥有的角色集合比如“admin,test”
Set<String> roleSet = commonApi.queryUserRoles(username);
System.out.println(roleSet.toString());
//System.out.println(roleSet.toString());
info.setRoles(roleSet);
// 设置用户拥有的权限集合比如“sys:role:add,sys:user:add”
Set<String> permissionSet = commonApi.queryUserAuths(username);
info.addStringPermissions(permissionSet);
System.out.println(permissionSet);
//System.out.println(permissionSet);
log.info("===============Shiro权限认证成功==============");
return info;
}

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

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

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

Loading…
Cancel
Save