
做管理系统,我们现在开发的大多数表单都是“与”的关系,比如前端有四个查询条件 a,b,c,d ,前端把这四个查询条件都传给我,我在查询接口里面会这么写:
if(a != null) { sql += " and table.a = a"; } if(b != null) { sql += " and table.b = b"; } 这样写是没有问题的,假如现在有这样一种场景,前端四个查询条件中 a 和 b 是或的关系,比如 a 指的是待筛选内容的创建者,b 指的是待筛选内容的处理者,那么根据创建者或处理者来查询是合理的需求,同时 a 、b 与 c 、d 又是与的关系,比如 c 指截止时间,d 指处理状态,那么这时按照上面的思路,拼 sql 就得这么拼
if(a != null) { if(b != null) { sql += " and (table.a = a or table.b = b)"; } else { sql += " and table.a = a"; } } if(b != null) { if(a != null) { sql += " and (table.a = a or table.b = b)"; } else { sql += " and table.b = b"; } 现在这样算是蒙混过关了(我现在就是这么处理的),但是我又想到一个问题,假如用户 或 的查询条件是不固定的,且前端只传给我一个对象,那这个 sql 拼接就是指数倍增加了。再假设,如果前端写了一个支持根据后台传过去的字段,由用户递归构造且/或查询条件的“且或组件”,这个组件支持用户自定义不确定数量的且或关系,支持自定义操作符,例如,传过来,不传 searchParam 对象,传过来是一个 Json 对象,类似以下:
{ "prop": "", "relation": "and", "condition": [ { "relation": "or", "condition": [ { "prop": "Name", "operation": ">", "value": "李华" }, { "prop": "Age", "operation": "=". "value": 24 } ] }, { "prop": "School", "operation": "=", "value": "大学" } ] } 这时候的 sql 该咋拼呢,感觉像是一道算法题?是不是直接后序遍历这个 json 结构,把对应拼接的 sql 直接放代码就可以了?算法比较弱,还没写,主要不知道括号往哪放,还是没遍历一层直接嵌套括号就可以了?
最后又回到开始的问题,如果一开始前端就传给我一个{},让我处理,有些字段是或,跟其他字段是且,这样的话我是不是就得写一大串 sql 了,有没有好点的办法,还是我在后台反射下,把不为 null 的给过滤出来,这样就不用判断一大坨为 null 的逻辑了,因为是维护老项目不好在序列化时把 null 去掉,还是说这个工作前端做比较好
1 irisdev OP 不小心点了发送了,有回复的先感谢一下,总感觉这个项目的这种写法太草台班子了,但是又没见过好的,希望开开眼 |
2 carrotliang 2024-10-31 07:47:18 +08:00 可以参考 mybatis 的解决方案 |
3 BugCry 2024-10-31 07:51:15 +08:00 via Android 已经等不及要注入了! |
4 chendy 2024-10-31 07:55:12 +08:00 mybatis 的硬拼方案 jpa 的 criteria 模式 手拼 sql 的除非场景简单单一否则就等着被灌满不是被注入吧 |
5 irisdev OP |
7 XuHuan1025 2024-10-31 08:22:39 +08:00 不是有那啥 graphsql 吗 |
8 irisdev OP @XuHuan1025 都是用的 post+json ,切换不现实了,而且 graphql 好像解决不了这个问题吧 |
9 sagaxu 2024-10-31 08:29:20 +08:00 这是典型的递归应用场景,把多个条件放入 list ,再用" or " / " and " 做分隔符拼接起来,每一层都用()包起来 |
10 Bingchunmoli 2024-10-31 08:42:18 +08:00 via Android mybatis |
11 miracleyao 2024-10-31 08:44:00 +08:00 mybatis-plus lambda |
12 EastLord 2024-10-31 08:56:34 +08:00 via iPhone mybatis 动态 sql JPA 也行 |
13 BrbiwsFtd9zDGZqB 2024-10-31 09:00:04 +08:00 巧了, lz 你说的这个东西我实现过. 我理解你的问题关键点不在于 sql 拼接, 而是如何将 动态的 json 转换为查询逻辑, 因为 sql 拼接有很多方式, mybatis 也好 jpa 也好 都是在转换为查询逻辑之后的具体实现. 所以我最终的实现方式中使用了 visitor 模式去分片组织查询逻辑. ```json { "paginate": true, "page": 1, "size": 20, "sort": [ { "field": "ceatedDate", "direction": "DESC" } ], "filter": { "match": "ALL", "rules": [ { "field": "name", "operator": "LK", "value": "111" }, { "match": "ANY", "rules": [ { "field": "title", "operator": "EQ", "value": "111" }, { "field": "isbn", "operator": "EQ", "value": "1" } ] } ] } } ``` ```sql SELECT * FROM book WHERE "name" LIKE ? ESCAPE'' AND ( title =? OR isbn =? ) ORDER BY created_date DESC OFFSET ? ROWS FETCH FIRST ? ROWS ONLY ``` |
14 csys 2024-10-31 09:04:48 +08:00 构造表达式语法树,然后根据表达式生成 sql 拼接表达式要比拼接 sql 容易的多 市面上应该有类似的库吧 |
15 xuanbg 2024-10-31 09:05:59 +08:00 别想太多,条件基本都是固定的。就算变,也不会频繁变。而且有上限,总不能用不存在的字段做条件吧?就算是,也是无效的啊。 所以硬拼 sql 没毛病。 |
16 wolfie 2024-10-31 09:07:17 +08:00 用 mybatis plus 定义一个 关联表基础 SQL ,外部拼接 `QueryWrapper` 根据入参数据结构,动态拼接 SQL ,多层级 json 可以搭配递归 拼接 QueryWrapper ``` java @Mapper public interface YourMapper { @Select("SELECT a.*, b.* FROM table_1 a LEFT JOIN table_2 b ON a.id = b.a_id ${ew.customSqlSegment}") List<Entity> selectWithJoin(@Param("ew") QueryWrapper<?> wrapper); } QueryWrapper<?> wrapper = new QueryWrapper<>(); wrapper .and(and -> { and.eq("table_1.field_1", "1").eq("table_2.field_2", "2"); }) .and(and -> { and.gt("table_1.field_2", 123).or().lt("table_1.field_2", 234); }); ``` |
17 sparklee 2024-10-31 09:12:04 +08:00 内网环境直接由前端传 SQL 得了 |
18 ltmst 2024-10-31 09:13:53 +08:00 c# 的做法就是实体类特性声明+表达式树,ORM 库都是这样实现的 其中的一个用法你可以参考 https://www.cnblogs.com/FreeSql/p/16485310.html Java 实现的话应该类似 |
19 Noicdi 2024-10-31 09:18:27 +08:00 SELECT * FROM table t WHERE (:a IS NULL OR t.a = :a) AND (:b IS NULL OR t.b = :b) |
20 chobitssp 2024-10-31 09:25:40 +08:00 |
21 hukei 2024-10-31 09:47:26 +08:00 额 没有 ORM 吗 交给框架就好 |
22 cheng6563 2024-10-31 09:53:33 +08:00 让前端传 SQL ,后台查的时候设置连接 readonly 。 注入随他注吧,别把表删了就行。 |
23 siweipancc 2024-10-31 09:53:52 +08:00 via iPhone 兄弟你好奔放啊,没被攻击过? |
24 sparklee 2024-10-31 09:59:56 +08:00 前端传 sql 语句中的 where 部分, 做好验证, 内网基本没啥大问题吧 |
25 nice2cu 2024-10-31 10:40:28 +08:00 |
26 adoal 2024-10-31 10:49:44 +08:00 大概率是业务需求没梳理清楚…… |
27 ccsert 2024-10-31 11:04:49 +08:00 可以看看 bean-searcher 这个框架,专门做复杂查询的 https://gitee.com/troyzhxu/bean-searcher |
28 NoKey 2024-10-31 11:15:58 +08:00 常规业务吧,产品经理可以定一下,哪些字段可以查询,可以限制一下,别太多,后端就好处理了 |
29 vcbal 2024-10-31 11:19:41 +08:00 标准 XY 问题 |
30 fzdfengzi 2024-10-31 11:21:31 +08:00 用表达式目录树动态拼 sql |
31 kingcanfish 2024-10-31 11:22:47 +08:00 如果是各种条件乱七八糟的复杂查询 是不是应该用 cel https://cel.dev/?hl=zh-cn |
32 815979670 2024-10-31 11:30:46 +08:00 |
34 siweipancc 2024-10-31 11:53:50 +08:00 via iPhone @815979670 搜索用户名我传两”_ ”进去 ,上家就是这么烂的 |
35 skallz 2024-10-31 11:57:21 +08:00 @siweipancc 说的对,就算内网用户没有任何恶意行为,也会因为预想之外的内容导致出现问题 |
36 treblex 2024-10-31 12:22:16 +08:00 https://github.com/LazyFury/SpringLearn/blob/master/kt/src/main/java/io/lazyfury/lucky_cat/common/helper/SearchHelper.kt 试试这个,url 格式类似 django ,用 jpa Specification 实现的, ![]() 我在 java 和 php 和 go 都尝试了类似的实现,最后选的 go ,所以这个代码不太严谨,但是能跑 |
37 815979670 2024-10-31 13:13:01 +08:00 @codehz @siweipancc @skallz 有道理 我忽略了非主观故意的场景,所以最好的方式还是走 SQL 预处理,通过占位符,使数据与 SQL 分离,这样即使非主观故意 也不会对业务产生影响 |
39 Sayuri 2024-10-31 13:34:59 +08:00 |
40 diagnostics 2024-10-31 13:35:43 +08:00 @iisdev #8 graphql 可以解决 |
41 importmeta 2024-10-31 13:52:24 +08:00 说个之前碰见的某国企的做法, 有个传公式, 另一个字段传数据. |
42 BigEarMosquito 2024-10-31 14:01:56 +08:00 |
43 lucasdev 2024-10-31 14:58:05 +08:00 可以看看 React Query Builder ( https://react-querybuilder.js.org/) 是怎么做的 ![]() Convert to 那里可以 - 选 SQL (parameterized) / SQL (named parameters),输出防注入的 SQL - 选 JSON (和你给的那个 JSON 格式差不多),但这种需要你自己构建语法树,再转成 SQL (也可以找找现成的三方库) |
44 Anakin078 2024-10-31 15:13:25 +08:00 @815979670 #32 并不能确保后台的认证、鉴权不会出问题。在有 SQL 注入和没有 SQL 注入的场景下,绕过认证鉴权访问后台的攻击影响是不一样的,在有 SQL 注入的场景下会危害到整库整表,甚至达到服务器的 RCE 。层层防护才能有效增加攻击成本,最大化的保障数据安全。 |
47 ninjamk200 2024-10-31 17:05:12 +08:00 这个 react 库不错 |
48 JYii 2024-10-31 17:08:58 +08:00 这么拼起来数据量上来了,sql 优化怎么办(小表当没看见 |
50 lianhuayu420 2024-11-09 16:12:39 +08:00 好好设计下关系,走 orm 吧,建议 https://github.com/babyfish-ct/jimmer 试试 |
51 zjc97816 2024-11-19 19:14:15 +08:00 Antlr4 试试呢 |