components添加前端组件searchSelect

This commit is contained in:
16358 2025-06-03 11:20:06 +08:00
parent 782cca521a
commit 463a18f209
10 changed files with 363 additions and 0 deletions

View File

@ -3,6 +3,9 @@ package com.ruoyi.common.utils.sql;
import com.ruoyi.common.exception.UtilException;
import com.ruoyi.common.utils.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* sql操作工具类
*
@ -25,6 +28,25 @@ public class SqlUtil
*/
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);
}
}
}

View File

@ -260,4 +260,23 @@ public class GenController extends BaseController
response.setContentType("application/octet-stream; charset=UTF-8");
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);
}
}

View File

@ -98,6 +98,10 @@ public class GenTable extends BaseEntity
/** 上级菜单名称字段 */
private String parentMenuName;
private String remark;
private Integer columnCount;
public Long getTableId()
{
return tableId;
@ -343,6 +347,14 @@ public class GenTable extends BaseEntity
return isSub(this.tplCategory);
}
public Integer getColumnCount() {
return columnCount;
}
public void setColumnCount(Integer columnCount) {
this.columnCount = columnCount;
}
public static boolean isSub(String tplCategory)
{
return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory);

View File

@ -19,6 +19,8 @@ public class GenTableColumn extends BaseEntity
/** 归属表编号 */
private Long tableId;
private String tableName;
/** 列名称 */
private String columnName;
@ -78,6 +80,14 @@ public class GenTableColumn extends BaseEntity
return columnId;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public void setTableId(Long tableId)
{
this.tableId = tableId;

View File

@ -1,6 +1,8 @@
package com.ruoyi.generator.mapper;
import java.util.List;
import java.util.Map;
import com.ruoyi.generator.domain.GenTable;
/**
@ -88,4 +90,6 @@ public interface GenTableMapper
* @return 结果
*/
public int createTable(String sql);
List<Map> getSearchSelectData(Map<String, Object> map);
}

View File

@ -528,4 +528,10 @@ public class GenTableServiceImpl implements IGenTableService
}
return genPath + File.separator + VelocityUtils.getFileName(template, table);
}
@Override
public List<Map> getSearchSelectData(Map<String, Object> map ) {
return genTableMapper.getSearchSelectData(map);
}
}

View File

@ -127,4 +127,6 @@ public interface IGenTableService
* @param genTable 业务信息
*/
public void validateEdit(GenTable genTable);
List<Map> getSearchSelectData(Map<String, Object> map );
}

View File

@ -174,6 +174,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="createTable">
${sql}
</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 gen_table

View File

@ -83,3 +83,11 @@ export function synchDb(tableName) {
method: 'get'
})
}
export function getSearchSelectData(param){
return request({
url: '/tool/gen/getSearchSelectData',
method: 'get',
params: param
})
}

View File

@ -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,whereorderby:"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>