应用场景
1/数据库任意单表查询的重复劳动
2/数据库任意单表多业务需要不同字段的重复劳动
#说明
1/黑名单设计,解决敏感表和敏感字段拦截
2/防SQL注入处理,对key,value的合法性进行验证
3/对于表名暴露给前端,有两种处理方式,一种由node层来传参,一种服务器添加一个表名映射,实现逻辑表与物理表的联系
4/为了性能考虑,fields与filter为必填项
5/可扩展:加入分页查询,可以实现任意表的分页查询
使用场景:
1/根据ID查询,返回id,name字段
{
"table":"t_base_user",
"fields":["id","name"],
"filter":{
"id":"1111"
}
}
2/in查询
{
"table":"t_base_user",
"fields":["id","name"],
"filter":{
"id":["1",2]
}
}
3/like查询
{
"table":"t_base_user",
"fields":["id","name"],
"filter":{
"name":{"$like":"%富贵%"}
}
}
4/范围查询 大于,小于
{
"table":"t_base_user",
"fields":["id","name"],
"filter":{
"start_time":{
"$gte":"2020-03-13 00:00:00",
"$lte":"2020-03-20 00:00:00"
}
}
}
5/反向查询,不等于
{
"table":"t_base_user",
"fields":["id","name"],
"filter":{
"type":{"$ne":1}
}
}
6/嵌套逻辑范围查询,$and,$or
{
"table":"t_base_user",
"fields":["id","name"],
"filter":{
"group_account":"001",
"$or":{
"type":{"$ne":1},
"name":{"$like":"富贵%"}
}
}
}
where group_account='001' and (type !=1 or name like '富贵%')
7/根据关键字模糊匹配,倒序前5条记录
{
"table":"t_base_user",
"fields":["id","name"],
"filter":{
"name":{"$like":"%宝贵%"}
},
"sort":{"time":"desc"},
"limit":5
}
#实体对象
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class QueryReq implements Serializable {
/**
* 数据源
*/
private String table;
/**
* 返回字段-as别名
* eg:["id","head_img as headImg"]
*/
@NotNull(message = "'fields' 不能为空")
private List<String> fields;
/**
* 过滤条件
* eg:
* {
* "org_account":"ZJXHJT",
* "start_time":{
* "$gte":"2020-03-13 00:00:00",
* "$gt":"2020-03-13 00:00:00",
* "$lte":"2020-03-20 00:00:00",
* "$lt":"2020-03-20 00:00:00"
* },
* "activity_type":{"$ne":2},
* "activity_name":{"$like":"%hello%"},
* "$and":{
* "activity_status":3,
* "project_account":"001"
* },
* "$or":{
* "activity_status":3,
* "project_account":"001"
* },
* "prize_configure":["1111","0001","1001"]* }
*/
@NotNull(message = "'filter' 不能为空")
private Map<String,Object> filter;
/**
* 返回类型 list | obj
*/
private String resultType = "list";
/**
* 返回记录数
*/
private Integer limit;
/**
* 排序,eg:{"id":"desc","name":"asc"}
*/
private LinkedHashMap<String,String> sort;
/**
* 是否显示SQL
*/
private boolean showSql = false;
}
#解析器,转SQL语句
@Service
@Slf4j
public class CommonService {
@Autowired
private CommonMapper commonMapper;
private final List<String> scope = Arrays.asList("$or","$and");
private final List<String> variables = Arrays.asList("$gt","$gte","$lt","$lte","$ne","$like");
private final List<String> sortEnum = Arrays.asList("asc","desc");
private static final Map<String,List<String>> blackTableFieldList = new HashMap<>();
private final Pattern keyRegexPat = Pattern.compile("[^(a-zA-Z 0-9_\\-\\$)]+");
private final Pattern valueRegexPat = Pattern.compile("[']+");
static
{
//黑名单字段描述
//blackTableFieldList.put("t_user", Arrays.asList("*"));
//blackTableFieldList.put("t_user", Arrays.asList("password"));
}
public Object query(QueryReq queryReq) {
validateKey(queryReq.getTable());
List<String> blankFieldList = blackTableFieldList.get(queryReq.getTable());
if (blankFieldList !=null && blankFieldList.contains("*")){
throw new BizException(ErrorCode.SERVER_ERROR.getCode(),"无权限访问此数据源任意字段");
}
queryReq.getFields().forEach(item->{
validateKey(item);
if (blankFieldList !=null && blankFieldList.contains(item)){
throw new BizException(ErrorCode.SERVER_ERROR.getCode(),"无权限访问此数据源,'"+item+"'字段");
}
});
if (queryReq.getTable() == null){
throw new BizException(ErrorCode.SERVER_ERROR.getCode(),"未知数据源");
}
StringBuilder sql = new StringBuilder("select ");
sql.append(String.join(",",queryReq.getFields()));
sql.append(" from ");
sql.append(queryReq.getTable());
sql.append(" where ");
sql.append(buildWhere("$and",queryReq.getFilter()));
sql.append(buildSort(queryReq.getSort()));
sql.append(!"list".equals(queryReq.getResultType())?" limit 1 ":queryReq.getLimit() == null?"":" limit "+queryReq.getLimit());
log.debug("generator sql:{}",sql.toString());
if (queryReq.isShowSql()){
return sql.toString();
}
try {
List<Object> resultList = commonMapper.query(sql.toString());
if (!"list".equals(queryReq.getResultType())){
return resultList.size()==0?Collections.EMPTY_MAP:resultList.get(0);
}
return resultList;
}catch (Exception e){
throw new BizException(ErrorCode.SERVER_ERROR.getCode(),e.getCause().getMessage());
}
}
private void validateKey(String item) {
Matcher matcher = keyRegexPat.matcher(item);
if(matcher.find()){
String subStr = scopeStr(item,matcher.start(0)-10,matcher.end(0)+10);
throw new BizException(ErrorCode.FORMAT_ERROR.getCode(),"包含特殊符号:'"+subStr+"'");
}
}
private void validateValue(String item) {
Matcher matcher = valueRegexPat.matcher(item);
if(matcher.find()){
String subStr = scopeStr(item,matcher.start(0)-10,matcher.end(0)+10);
throw new BizException(ErrorCode.FORMAT_ERROR.getCode(),"包含特殊符号:'"+subStr+"'");
}
}
private String scopeStr(String item, int start, int end) {
start = start<0?0:start;
end = end>item.length()?item.length():end;
return item.substring(start,end);
}
private String buildSort(LinkedHashMap<String,String> sort) {
if (CollectionUtils.isEmpty(sort)){
return "";
}
StringBuilder valStr = new StringBuilder(" order by ");
boolean isFirst = true;
for(String item : sort.keySet()){
validateKey(item);
if (!sortEnum.contains(sort.get(item))){
throw new BizException(ErrorCode.FORMAT_ERROR.getCode(),"包含未知匹配符:"+sort.get(item));
}
if (!isFirst){
valStr.append(",");
}
isFirst = false;
valStr.append(item).append(" ").append(sort.get(item)).append(" ");
}
return valStr.toString();
}
private String buildWhere(String connector,Map<String, Object> filter) {
connector = connector.replace("$","");
StringBuilder valStr = new StringBuilder();
boolean isFirst = true;
for(String key:filter.keySet()){
validateKey(key);
if (!isFirst){
valStr.append(" ").append(connector).append(" ");
}
isFirst = false;
if (scope.contains(key)){
valStr.append("(").append(buildWhere(key, (Map<String, Object>) filter.get(key))).append(")");
}else if(filter.get(key) instanceof Map){
valStr.append(buildVariableValue(key,(Map<String, Object>) filter.get(key)));
}else {
valStr.append(key).append(buildValue(filter.get(key)));
}
}
return valStr.toString();
}
private String buildVariableValue(String key, Map<String, Object> filter) {
StringBuilder valStr = new StringBuilder();
boolean isFirst = true;
for(String item :filter.keySet()){
if (!variables.contains(item)){
throw new BizException(ErrorCode.FORMAT_ERROR.getCode(),"包含未知匹配符:"+item);
}
String connector = "";
switch (item){
case "$gte":connector=" >= ";break;
case "$gt":connector=" > ";break;
case "$lte":connector=" <= ";break;
case "$lt":connector=" < ";break;
case "$ne":connector=" != ";break;
case "$like":connector=" like ";break;
}
if (!isFirst){
valStr.append(" and ");
}
isFirst = false;
valStr.append(key).append(connector).append(buildStrValue(filter.get(item)));
}
return valStr.toString();
}
private String buildValue(Object val) {
StringBuilder valStr = new StringBuilder();
if (val instanceof Collection){
valStr.append(((Collection)val).stream().map(item->buildStrValue(item)).collect(Collectors.joining(","," in (",")")));
}else {
valStr.append(" = ").append(buildStrValue(val));
}
return valStr.toString();
}
private String buildStrValue(Object val){
if (val instanceof Number){
return val.toString();
}
validateValue(val.toString());
return "'"+val.toString()+"'";
}
}

514





