components添加前端组件searchSelect
This commit is contained in:
parent
782cca521a
commit
463a18f209
|
@ -3,6 +3,9 @@ package com.ruoyi.common.utils.sql;
|
||||||
import com.ruoyi.common.exception.UtilException;
|
import com.ruoyi.common.exception.UtilException;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sql操作工具类
|
* sql操作工具类
|
||||||
*
|
*
|
||||||
|
@ -25,6 +28,25 @@ public class SqlUtil
|
||||||
*/
|
*/
|
||||||
private static final int ORDER_BY_MAX_LENGTH = 500;
|
private static final int ORDER_BY_MAX_LENGTH = 500;
|
||||||
|
|
||||||
|
private final static String XSS_STR = "and |extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|user()";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正则 user() 匹配更严谨
|
||||||
|
*/
|
||||||
|
private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)";
|
||||||
|
/**正则 show tables*/
|
||||||
|
private final static String SHOW_TABLES = "show\\s+tables";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sleep函数
|
||||||
|
*/
|
||||||
|
private final static Pattern FUN_SLEEP = Pattern.compile("sleep\\(.*\\)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sql注释的正则
|
||||||
|
*/
|
||||||
|
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查字符,防止注入绕过
|
* 检查字符,防止注入绕过
|
||||||
*/
|
*/
|
||||||
|
@ -67,4 +89,90 @@ public class SqlUtil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回查询表名
|
||||||
|
* <p>
|
||||||
|
* sql注入过滤处理,遇到注入关键字抛异常
|
||||||
|
*
|
||||||
|
* @param table
|
||||||
|
*/
|
||||||
|
private static Pattern tableNamePattern = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{0,63}$");
|
||||||
|
public static String getSqlInjectTableName(String table) {
|
||||||
|
table = table.trim();
|
||||||
|
/**
|
||||||
|
* 检验表名是否合法
|
||||||
|
*
|
||||||
|
* 表名只能由字母、数字和下划线组成。
|
||||||
|
* 表名必须以字母开头。
|
||||||
|
* 表名长度通常有限制,例如最多为 64 个字符。
|
||||||
|
*/
|
||||||
|
boolean isValidTableName = tableNamePattern.matcher(table).matches();
|
||||||
|
if (!isValidTableName) {
|
||||||
|
String errorMsg = "表名不合法,存在SQL注入风险!--->" + table;
|
||||||
|
throw new UtilException(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
//进一步验证是否存在SQL注入风险
|
||||||
|
filterContent(table, null);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sql注入过滤处理,遇到注入关键字抛异常
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static void filterContent(String value, String customXssString) {
|
||||||
|
if (value == null || "".equals(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 校验sql注释 不允许有sql注释
|
||||||
|
checkSqlAnnotation(value);
|
||||||
|
// 统一转为小写
|
||||||
|
value = value.toLowerCase();
|
||||||
|
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||||
|
//value = value.replaceAll("/\\*.*\\*/","");
|
||||||
|
|
||||||
|
String[] xssArr = XSS_STR.split("\\|");
|
||||||
|
for (int i = 0; i < xssArr.length; i++) {
|
||||||
|
if (value.indexOf(xssArr[i]) > -1) {
|
||||||
|
throw new UtilException("请注意,值可能存在SQL注入风险!--->" + 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) {
|
||||||
|
throw new UtilException("请注意,值可能存在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 UtilException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验是否有sql注释
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static void checkSqlAnnotation(String str){
|
||||||
|
Matcher matcher = SQL_ANNOTATION.matcher(str);
|
||||||
|
if(matcher.find()){
|
||||||
|
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
||||||
|
throw new UtilException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// issues/4737 sys/duplicate/check SQL注入 #4737
|
||||||
|
Matcher sleepMatcher = FUN_SLEEP.matcher(str);
|
||||||
|
if(sleepMatcher.find()){
|
||||||
|
String error = "请注意,值可能存在SQL注入风险---> sleep";
|
||||||
|
throw new UtilException(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,4 +260,23 @@ public class GenController extends BaseController
|
||||||
response.setContentType("application/octet-stream; charset=UTF-8");
|
response.setContentType("application/octet-stream; charset=UTF-8");
|
||||||
IOUtils.write(data, response.getOutputStream());
|
IOUtils.write(data, response.getOutputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询业务列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/getSearchSelectData")
|
||||||
|
public AjaxResult getSearchSelectData( String tableName, String value, String label, String where, String groupby, String orderby)
|
||||||
|
{
|
||||||
|
orderby = SqlUtil.escapeOrderBySql(orderby);
|
||||||
|
tableName = SqlUtil.getSqlInjectTableName(tableName);
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("tableName", tableName);
|
||||||
|
map.put("value", SqlUtil.getSqlInjectTableName(value));
|
||||||
|
map.put("label", SqlUtil.getSqlInjectTableName(label));
|
||||||
|
map.put("where", where);
|
||||||
|
map.put("groupby", groupby);
|
||||||
|
map.put("orderby", orderby);
|
||||||
|
List<Map> list = genTableService.getSearchSelectData(map);
|
||||||
|
return AjaxResult.success(list);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -98,6 +98,10 @@ public class GenTable extends BaseEntity
|
||||||
/** 上级菜单名称字段 */
|
/** 上级菜单名称字段 */
|
||||||
private String parentMenuName;
|
private String parentMenuName;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
private Integer columnCount;
|
||||||
|
|
||||||
public Long getTableId()
|
public Long getTableId()
|
||||||
{
|
{
|
||||||
return tableId;
|
return tableId;
|
||||||
|
@ -343,6 +347,14 @@ public class GenTable extends BaseEntity
|
||||||
return isSub(this.tplCategory);
|
return isSub(this.tplCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getColumnCount() {
|
||||||
|
return columnCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumnCount(Integer columnCount) {
|
||||||
|
this.columnCount = columnCount;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isSub(String tplCategory)
|
public static boolean isSub(String tplCategory)
|
||||||
{
|
{
|
||||||
return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory);
|
return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory);
|
||||||
|
|
|
@ -19,6 +19,8 @@ public class GenTableColumn extends BaseEntity
|
||||||
/** 归属表编号 */
|
/** 归属表编号 */
|
||||||
private Long tableId;
|
private Long tableId;
|
||||||
|
|
||||||
|
private String tableName;
|
||||||
|
|
||||||
/** 列名称 */
|
/** 列名称 */
|
||||||
private String columnName;
|
private String columnName;
|
||||||
|
|
||||||
|
@ -78,6 +80,14 @@ public class GenTableColumn extends BaseEntity
|
||||||
return columnId;
|
return columnId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTableName() {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableName(String tableName) {
|
||||||
|
this.tableName = tableName;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTableId(Long tableId)
|
public void setTableId(Long tableId)
|
||||||
{
|
{
|
||||||
this.tableId = tableId;
|
this.tableId = tableId;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.ruoyi.generator.mapper;
|
package com.ruoyi.generator.mapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.ruoyi.generator.domain.GenTable;
|
import com.ruoyi.generator.domain.GenTable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,4 +90,6 @@ public interface GenTableMapper
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public int createTable(String sql);
|
public int createTable(String sql);
|
||||||
|
|
||||||
|
List<Map> getSearchSelectData(Map<String, Object> map);
|
||||||
}
|
}
|
||||||
|
|
|
@ -528,4 +528,10 @@ public class GenTableServiceImpl implements IGenTableService
|
||||||
}
|
}
|
||||||
return genPath + File.separator + VelocityUtils.getFileName(template, table);
|
return genPath + File.separator + VelocityUtils.getFileName(template, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Map> getSearchSelectData(Map<String, Object> map ) {
|
||||||
|
|
||||||
|
return genTableMapper.getSearchSelectData(map);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -127,4 +127,6 @@ public interface IGenTableService
|
||||||
* @param genTable 业务信息
|
* @param genTable 业务信息
|
||||||
*/
|
*/
|
||||||
public void validateEdit(GenTable genTable);
|
public void validateEdit(GenTable genTable);
|
||||||
|
|
||||||
|
List<Map> getSearchSelectData(Map<String, Object> map );
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
${sql}
|
${sql}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<select id="getSearchSelectData" resultType="java.util.Map">
|
||||||
|
select
|
||||||
|
${value} as value,
|
||||||
|
${label} as label
|
||||||
|
from
|
||||||
|
${tableName}
|
||||||
|
<if test="where != null and where != ''">
|
||||||
|
where ${where}
|
||||||
|
</if>
|
||||||
|
<if test="groupby != null and groupby != ''">
|
||||||
|
group by ${groupby}
|
||||||
|
</if>
|
||||||
|
<if test="orderby != null and orderby != ''">
|
||||||
|
order by ${orderby}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
<update id="updateGenTable" parameterType="GenTable">
|
<update id="updateGenTable" parameterType="GenTable">
|
||||||
update gen_table
|
update gen_table
|
||||||
<set>
|
<set>
|
||||||
|
|
|
@ -83,3 +83,11 @@ export function synchDb(tableName) {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSearchSelectData(param){
|
||||||
|
return request({
|
||||||
|
url: '/tool/gen/getSearchSelectData',
|
||||||
|
method: 'get',
|
||||||
|
params: param
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-select
|
||||||
|
filterable
|
||||||
|
slot="reference"
|
||||||
|
ref="select"
|
||||||
|
:size="size"
|
||||||
|
v-model="selectedData"
|
||||||
|
:multiple="multiple"
|
||||||
|
:clearable="clearable"
|
||||||
|
:disabled="disabled"
|
||||||
|
@change="changeSelected"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in options" :key="item.label+'-'+item.value" :label="item.label" :value="item.value" >
|
||||||
|
<span style="float: left">{{ item.label }}</span>
|
||||||
|
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.value }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {getSearchSelectData } from '@/api/tool/gen';
|
||||||
|
export default {
|
||||||
|
name: 'search-select',
|
||||||
|
props: {
|
||||||
|
value: [Number, String],
|
||||||
|
// 参数:表名,value,label,where条件,orderby排序:"t_bd_factory,id,name"
|
||||||
|
params: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderby: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
groupby: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultProps: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 配置是否可多选
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 配置是否可清空选择
|
||||||
|
clearable: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return 'small';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default() {
|
||||||
|
return 250;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default() {
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scope: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
options: [],
|
||||||
|
selectedData: [], // 选中的节点
|
||||||
|
style: 'width:' + this.width + 'px;' + 'height:' + this.height + 'px;',
|
||||||
|
selectStyle: 'width:' + (this.width + 24) + 'px;',
|
||||||
|
checkedIds: [],
|
||||||
|
checkedData: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initData();
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
this.selectedData = val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
this.initData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
this.initData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData(){
|
||||||
|
if (!this.params) return;
|
||||||
|
let tableName = this.params.split(",")[0];
|
||||||
|
let value = this.params.split(",")[1];
|
||||||
|
let label = this.params.split(",")[2];
|
||||||
|
getSearchSelectData({
|
||||||
|
tableName: tableName,
|
||||||
|
value: value,
|
||||||
|
label: label,
|
||||||
|
where: this.where,
|
||||||
|
groupby: this.groupby,
|
||||||
|
orderby: this.orderby
|
||||||
|
}).then(res=>{
|
||||||
|
console.log(res)
|
||||||
|
if(res.code == 200) {
|
||||||
|
this.options = res.data;
|
||||||
|
//
|
||||||
|
console.log(this.value)
|
||||||
|
if(this.value) {
|
||||||
|
this.selectedData = this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 选中的select选项改变的回调 {label: "名称", value: "值"}
|
||||||
|
changeSelected(selectedData) {
|
||||||
|
this.$emit('input', selectedData)
|
||||||
|
if (this.multiple) {
|
||||||
|
this.$emit('change', this.options.filter(a=> selectedData.includes(a.value)));
|
||||||
|
} else {
|
||||||
|
let result = this.options.find(a=>a.value == selectedData);
|
||||||
|
result['scope'] = this.scope;
|
||||||
|
this.$emit('change', result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
Loading…
Reference in New Issue