mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-08 17:12:28 +08:00
Compare commits
548 Commits
v3.4.2
...
v3.7.0_spr
| Author | SHA1 | Date | |
|---|---|---|---|
| 1632c241ee | |||
| e9d05b0e75 | |||
| 6ade7e22f8 | |||
| 43d47c08cb | |||
| 3d3b5850ad | |||
| 816eeb9225 | |||
| 0b42efbbbf | |||
| 56da1a23c2 | |||
| d3c5a58db9 | |||
| d44945f688 | |||
| f370855683 | |||
| c582efd115 | |||
| c687c7a916 | |||
| 8e44080b00 | |||
| 56ca53cc93 | |||
| e616c5d8fe | |||
| 0075ec6751 | |||
| 7e31341f1a | |||
| d4ca2eb934 | |||
| 84946888ad | |||
| d276c3d8ad | |||
| 7dccaf2ec8 | |||
| ae1b8d4654 | |||
| 49f709a32a | |||
| 335c369546 | |||
| 1a446007c7 | |||
| 76f9575140 | |||
| fd7783644f | |||
| f6a3e11aa2 | |||
| c2768cea85 | |||
| 2d37b166a2 | |||
| cff5ec5a40 | |||
| 0947a199b6 | |||
| 2ecfe74b33 | |||
| 6147e923de | |||
| 3a07d5a983 | |||
| 2747868ea7 | |||
| dcc27d71b4 | |||
| 30c0482b6d | |||
| 641298b32d | |||
| 09a536f549 | |||
| 35ab818741 | |||
| e00358859c | |||
| aa24978d2b | |||
| ee497a8d1e | |||
| 935575576f | |||
| b45e75007b | |||
| 52ae0c359e | |||
| 06e6594b75 | |||
| 73d62b484c | |||
| 0ff93a220d | |||
| 2071b5bcc4 | |||
| e1378f4ee5 | |||
| a6b6e7c9d4 | |||
| 857fb53fa1 | |||
| 9db6c1a7ac | |||
| fd0461644e | |||
| 10263720d4 | |||
| cddf23c787 | |||
| 70a37309dd | |||
| 7548f3aa60 | |||
| 48555b5219 | |||
| 06d58f202f | |||
| 628870af9b | |||
| b8e0d4391d | |||
| ad3d2eb3fc | |||
| 72b34d082b | |||
| b46a6438e6 | |||
| 5488f99723 | |||
| 6bc1fe8d21 | |||
| d0406fcd83 | |||
| 9159b55096 | |||
| 7cac16320c | |||
| 24dbd1db39 | |||
| 7112649a21 | |||
| fbc312c35d | |||
| b8162a4a6d | |||
| faebdee755 | |||
| 2fc672dfab | |||
| 4dc4e87900 | |||
| 13d00a8bb4 | |||
| fb95cf7f2f | |||
| 78f048fda5 | |||
| 200adb8490 | |||
| f1496b5084 | |||
| 7b06715bff | |||
| 28404d2fd3 | |||
| 46b026b989 | |||
| 3091d5b6f0 | |||
| c117abb2d4 | |||
| 94c45f5e0f | |||
| c92c9be49a | |||
| dbc3f13c65 | |||
| ea6927a2a7 | |||
| b69a716b04 | |||
| 7e71fa26d7 | |||
| 58e85e0569 | |||
| 6fc34d8a39 | |||
| 4fed40ff7d | |||
| ee4ff35c90 | |||
| c9b92decaf | |||
| eed3bc346d | |||
| 6edef14f07 | |||
| ab49983759 | |||
| 5a09a6fb4a | |||
| ac93bf7d6b | |||
| 790df934b5 | |||
| c9c6dd5c1d | |||
| e3e1cd6b0d | |||
| 8aee4011a2 | |||
| 8950e19d4e | |||
| 99eb88f71c | |||
| 6e0277c60a | |||
| e923654161 | |||
| f3cf90bd28 | |||
| 06b41ae479 | |||
| 70847d17f1 | |||
| 2cfc39b23f | |||
| c8676b3040 | |||
| 73bd04d04a | |||
| 0ca4badb77 | |||
| 80b92ca132 | |||
| 824d7839d8 | |||
| 58865bef28 | |||
| 9fd40d0973 | |||
| c88f9d95d4 | |||
| 266ebd9122 | |||
| fee729e16c | |||
| 10a3e9c6ba | |||
| 990f79fdfe | |||
| 6360aee0ff | |||
| beb0bc2f64 | |||
| 685b81e5ec | |||
| e38e395436 | |||
| f741db874c | |||
| d684c09392 | |||
| 39af6e25ee | |||
| b15e6e0422 | |||
| 8f99a80352 | |||
| 364be22dd0 | |||
| 936a87e738 | |||
| 2af165b201 | |||
| 3d5efc07ad | |||
| 21e8d640d2 | |||
| b0ce456909 | |||
| b8e1306955 | |||
| 37a38ad288 | |||
| 9ee05c9510 | |||
| 275a68bb6a | |||
| f3f70e8549 | |||
| e15e9d80c4 | |||
| f7538c1ed8 | |||
| a9dba08a8d | |||
| 11af85d87a | |||
| 4caff75cce | |||
| 20efa3bf9a | |||
| c7977dda3d | |||
| 811861a957 | |||
| 24623ba4b0 | |||
| 7c68b46943 | |||
| c27c5a9a9b | |||
| 0ab280f812 | |||
| c3066dac17 | |||
| b650d512b3 | |||
| acf0713385 | |||
| 7c34161369 | |||
| bc52aa918d | |||
| cee872000a | |||
| 4a857680d0 | |||
| a47d0984dc | |||
| 925ec9447d | |||
| 411a73c1bf | |||
| 84077e6e24 | |||
| 184cf97304 | |||
| 9dfdd47b36 | |||
| 272a7540eb | |||
| ad796f079f | |||
| e7e7716d05 | |||
| 5f425b49b2 | |||
| 3ac8ee304a | |||
| e333b126b6 | |||
| 3618842f44 | |||
| c5d620d2b2 | |||
| cdea05ebb0 | |||
| fd8c848c9e | |||
| 09614a0239 | |||
| ca9a433f3c | |||
| 2be6052cd4 | |||
| 68ed67ee49 | |||
| d5903ba52a | |||
| 3ee635eddf | |||
| 21bc68fb53 | |||
| 6fe8f1d81a | |||
| 0bd7f715c4 | |||
| 0faac01bb7 | |||
| 74d88a8fcc | |||
| 041d88161e | |||
| 79a62aa056 | |||
| b86b4d9676 | |||
| aeaac80012 | |||
| e0ef20cf08 | |||
| f532e57862 | |||
| 169a66f5dd | |||
| 7e39b31123 | |||
| 18765450a6 | |||
| dff8c84d9c | |||
| d962c34846 | |||
| da08adbea1 | |||
| 46e3e62b59 | |||
| cd9794d818 | |||
| 5034b7cf18 | |||
| fdde84c68a | |||
| 4c54ff6f52 | |||
| de3285dc1b | |||
| 7f0c035c4c | |||
| 43593e8def | |||
| 48b0b608d8 | |||
| 69287a772b | |||
| 3656264f8a | |||
| 3361d48cd4 | |||
| ed86ea3da1 | |||
| 3deb0e5487 | |||
| 9e4792941e | |||
| b5fd5fe782 | |||
| 33c0104a02 | |||
| 81ed5100af | |||
| 87f9dc0064 | |||
| b311fedc6b | |||
| 337d5a9489 | |||
| cfeb81ee1e | |||
| 09f92f01aa | |||
| 6d1094936b | |||
| 8836a2793a | |||
| e321a0405f | |||
| c36ece8923 | |||
| a82213b90c | |||
| 98facdd2ee | |||
| d080b0b5ea | |||
| d8bc74794d | |||
| 338902ca0c | |||
| 732f05dc74 | |||
| 7ae6a11cf0 | |||
| cdbe1cb1a9 | |||
| 6ce92798c6 | |||
| f4454e9348 | |||
| d9134ae0c8 | |||
| 25180e41c8 | |||
| a99e3f2268 | |||
| d27c354bf1 | |||
| d818b1dd9d | |||
| bcdbec0091 | |||
| 098bb12b9e | |||
| 4a6c750b19 | |||
| d396e5304a | |||
| 9bed25be8c | |||
| 232037ec58 | |||
| b18c1120ab | |||
| f6a7831963 | |||
| 08a4473bbc | |||
| 9008ddafa4 | |||
| 7012ed4c2c | |||
| 1d8c8c30d0 | |||
| 7109b42092 | |||
| 1667b14194 | |||
| e9514873d2 | |||
| 090f790df4 | |||
| 82d051f388 | |||
| 5a3631c332 | |||
| c00b5526c3 | |||
| 2e35abd3a3 | |||
| 9bc3f6c56e | |||
| 5588912b62 | |||
| 37cf913d6d | |||
| 94ba767090 | |||
| a406c7cd81 | |||
| 480878a3db | |||
| a85499119d | |||
| 4cbe9cad8e | |||
| 2dfc06c679 | |||
| a591ad9fed | |||
| 5420e69b59 | |||
| f157c96f65 | |||
| 9588ace87f | |||
| cc57ac379b | |||
| 074920552a | |||
| 2a00a24058 | |||
| 06cc4ec0fe | |||
| decea393a5 | |||
| 65b0fab80d | |||
| 1d4098ae14 | |||
| 5c4f303a0d | |||
| 8dcc5bdf8a | |||
| b3e4a73a34 | |||
| 60b4a038f6 | |||
| 4e3738100a | |||
| 8216889078 | |||
| 0ee090664e | |||
| 4a9eda4ab0 | |||
| 2416c8b251 | |||
| 5b056f9dd6 | |||
| a93998dc56 | |||
| 268c27a782 | |||
| 23ace2712a | |||
| 157feeb925 | |||
| 4e25d4162f | |||
| 47a68f31e1 | |||
| 6ab4ee6a91 | |||
| a8dde73a8c | |||
| b2b11611c1 | |||
| 04c55fa3ba | |||
| 4957330c1c | |||
| ea9dfd6ea7 | |||
| 60f7191659 | |||
| 6f560c1cd7 | |||
| 524cd4a3d1 | |||
| f342c93eec | |||
| a364025bd2 | |||
| 5fa6a5e686 | |||
| 6d34164a16 | |||
| e15335ddab | |||
| b373a80498 | |||
| f0fb3ed5ff | |||
| 411deee8af | |||
| b2942e0323 | |||
| 78e371ab9f | |||
| ffeb607ad3 | |||
| 7dd68068a1 | |||
| 51d7f3b06b | |||
| a060db5530 | |||
| 30caf045ed | |||
| 6264b2ec70 | |||
| 9d1f73b3f7 | |||
| d557aa4cc0 | |||
| 81351aeb7a | |||
| ee1a568bed | |||
| e721668eb0 | |||
| 5edb30c51f | |||
| 9c5bd7193f | |||
| 3e9b7ccfc1 | |||
| f1717c7000 | |||
| 946d535f68 | |||
| 1a326cb70f | |||
| 5a9344b190 | |||
| 4eaebd658b | |||
| 581047c569 | |||
| 87677df925 | |||
| 56e81fbf7b | |||
| 473875a9d2 | |||
| 44952c79c2 | |||
| 58aebdbba4 | |||
| 2411d85af4 | |||
| 572ea6dd69 | |||
| 6e417a22ba | |||
| 3478e8f7bc | |||
| f9982a9132 | |||
| d3238205e1 | |||
| ff083361d4 | |||
| 9c038a979d | |||
| 5974a2e8a4 | |||
| 30151a2324 | |||
| 28293aba7d | |||
| d36caf8c69 | |||
| e6bd2d5009 | |||
| baf4b96b3f | |||
| acb48179ab | |||
| 20889e8724 | |||
| 751b81c7bf | |||
| 0bc7e0967d | |||
| 05181754bb | |||
| 5ab62d5d3b | |||
| 83d96bf89e | |||
| 0b2c966a72 | |||
| ffda530daa | |||
| 4c0641f374 | |||
| 54c7c034a4 | |||
| 7167668c2a | |||
| 1a722fcb07 | |||
| 648e66d5ef | |||
| e6e6902e85 | |||
| 04dd6b056f | |||
| 96b3ba6e9a | |||
| 8d22362fff | |||
| 436bfbced9 | |||
| 5273b81f94 | |||
| 6134db4984 | |||
| 6e7d9e8e5d | |||
| 741dc958f1 | |||
| 101ffd91af | |||
| cecbc3f628 | |||
| dd7bf104e7 | |||
| 6e85584c43 | |||
| c579dbb24d | |||
| 452132ca38 | |||
| ebc396340e | |||
| 5946aa79c3 | |||
| 2bc20aaa5e | |||
| 793295a8fe | |||
| eaf3e32fdd | |||
| ba40a797df | |||
| 545679f999 | |||
| 1a74dd1a2e | |||
| 9dd8b05fd7 | |||
| e273008b37 | |||
| 53684d224e | |||
| f9e843571c | |||
| 14e9adb3af | |||
| fedb7bf46f | |||
| ce520be8ca | |||
| 9d92c316c1 | |||
| f077cafcfe | |||
| fd7d0b7202 | |||
| 96586517bd | |||
| 5b5a2c0180 | |||
| b089bb96f1 | |||
| 9da4850bd7 | |||
| a6bcb376fc | |||
| 7abc07424b | |||
| e9f2bbca23 | |||
| b0ede05cf4 | |||
| ca1218f792 | |||
| 8ed2da601b | |||
| 08696dbb5f | |||
| 645ea40bfd | |||
| b970af00c1 | |||
| 5fe9508428 | |||
| a302fcd963 | |||
| 5258228d3b | |||
| 1c00be39f0 | |||
| cf1fe01d03 | |||
| 4c78bad14f | |||
| ebcddad130 | |||
| 42589cd1dc | |||
| d81fa1f908 | |||
| 217fe0dce4 | |||
| fffa7f327b | |||
| 5417f1285a | |||
| b75cbcc911 | |||
| a68f304307 | |||
| 0a00bd8a7c | |||
| e90efbb52f | |||
| 3c320a5769 | |||
| 98472b5c35 | |||
| 3f2354a8ac | |||
| 8a94c40e0e | |||
| 42eedcd3b3 | |||
| 3c2038ac0e | |||
| c8323540cc | |||
| ec80530afc | |||
| c76e7f344c | |||
| 5aca8260dc | |||
| c4baa2ecb8 | |||
| 420375ecab | |||
| f07e1c02e9 | |||
| eb054736a5 | |||
| 9d771b5d9b | |||
| d0a6282ce9 | |||
| d84cf92b03 | |||
| b679fb75ba | |||
| a3c0127a7a | |||
| c46ec2415b | |||
| f0e060f12e | |||
| 65a0d3b9df | |||
| 1a48fb86bc | |||
| 0fc374de47 | |||
| f94c5e1f3f | |||
| 352bd3e98f | |||
| 22fe4475d0 | |||
| 7a3d1f1a58 | |||
| 3159c822c7 | |||
| fa0ce6e7e1 | |||
| c29794d07b | |||
| 6ba5536554 | |||
| e6b93c2fca | |||
| b362147577 | |||
| 347b13beb4 | |||
| 16b8aac08f | |||
| b0b517e71e | |||
| cf3d746d63 | |||
| 0fdbf642d8 | |||
| 0ebd1df81d | |||
| 6f90a09558 | |||
| f3b77442fc | |||
| 47c6a9ba6a | |||
| 3e96c7e376 | |||
| 1a902d94c9 | |||
| fb1cef7cd3 | |||
| 83f909daca | |||
| 036b51cee6 | |||
| 8da82d8269 | |||
| c981c45a14 | |||
| 91848b98b7 | |||
| ec262cff53 | |||
| 9745b7e3ee | |||
| 97736e4b5d | |||
| 12991c83cb | |||
| 2a99fa2ecb | |||
| 4a1ed660ca | |||
| 8632a835c2 | |||
| 958cf01649 | |||
| 01602bd60a | |||
| 14c69fa533 | |||
| 9f6c89a983 | |||
| 51e2227bfe | |||
| ff77973a6c | |||
| f18ced524c | |||
| d34614c422 | |||
| c5e54f66a0 | |||
| 422dea8ddf | |||
| 31ba9e8343 | |||
| 5a303dfc4a | |||
| d7269976e9 | |||
| 3eab5ee9e6 | |||
| fb8afcda7d | |||
| feb7dddd65 | |||
| ab81c8d3a7 | |||
| 7c1daee18c | |||
| 578940aa40 | |||
| 92ea7282e6 | |||
| 4667e9db40 | |||
| 5558c30223 | |||
| 97842dc619 | |||
| a7c3e6e3dd | |||
| 3c489e3d98 | |||
| 951c7ebc79 | |||
| c321002504 | |||
| 708579b36d | |||
| 949f1e8e33 | |||
| 1725fc56f3 | |||
| ee493c2728 | |||
| af68147848 | |||
| 5dab553e3b | |||
| 6aa9fff66d | |||
| 63af9e4ca9 | |||
| 3e146061dd | |||
| 3580f50b07 | |||
| 99e5160436 | |||
| 5c00114d7c | |||
| 394c0e2caf | |||
| cb8adfb236 | |||
| d5ef6106db | |||
| 04486dca56 | |||
| c78c5dda4d | |||
| 06bea53726 | |||
| fbfe4962b3 | |||
| 28f007c13c | |||
| ae74abe727 | |||
| 53b670befd | |||
| 7690f0798a |
@ -1,21 +0,0 @@
|
||||
##### 版本号:
|
||||
|
||||
|
||||
##### 前端版本:vue3版?还是 vue2版?
|
||||
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
|
||||
##### 截图&代码:
|
||||
|
||||
|
||||
|
||||
|
||||
#### 友情提示(为了提高issue处理效率):
|
||||
- 未按格式要求发帖,会被直接删掉;
|
||||
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;
|
||||
- 请自己初判问题描述是否清楚,是否方便我们调查处理;
|
||||
- 针对问题请说明是Online在线功能(需说明用的主题模板),还是生成的代码功能;
|
||||
|
||||
|
||||
18
.github/ISSUE_TEMPLATE.md
vendored
18
.github/ISSUE_TEMPLATE.md
vendored
@ -1,21 +1,13 @@
|
||||
##### 版本号:
|
||||
|
||||
|
||||
##### 前端版本:vue3版?还是 vue2版?
|
||||
|
||||
|
||||
##### 问题描述:
|
||||
|
||||
|
||||
##### 截图&代码:
|
||||
##### 错误截图:
|
||||
|
||||
|
||||
|
||||
|
||||
#### 友情提示(为了提高issue处理效率):
|
||||
- 未按格式要求发帖,会被直接删掉;
|
||||
- 描述过于简单或模糊,导致无法处理的,会被直接删掉;
|
||||
- 请自己初判问题描述是否清楚,是否方便我们调查处理;
|
||||
- 针对问题请说明是Online在线功能(需说明用的主题模板),还是生成的代码功能;
|
||||
|
||||
|
||||
#### 友情提示:
|
||||
- 未按格式要求发帖、描述过于简抽象的,会被直接删掉;
|
||||
- 请确保问题描述清楚,方便我们理解并一次性调查解决问题;
|
||||
- 如果使用的不是master,请说明你使用的那个分支
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -9,3 +9,8 @@ rebel.xml
|
||||
|
||||
## front
|
||||
**/*.lock
|
||||
os_del.cmd
|
||||
os_del_doc.cmd
|
||||
.svn
|
||||
derby.log
|
||||
*.log
|
||||
5
LICENSE
5
LICENSE
@ -207,7 +207,10 @@
|
||||
本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。
|
||||
|
||||
1.允许基于本平台软件开展业务系统开发。
|
||||
2.不得基于该平台软件的基础,修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售,或与JeecgBoot参与同类软件产品市场的竞争。
|
||||
2.JeecgBoot底层依赖的非开源功能:online lib依赖、仪表盘lib依赖等,统一采用LGPL开源协议(不二次改造、不拆分出jeecgboot之外使用,就不产生侵权)
|
||||
3.不得基于该平台软件的基础,修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售,或与JeecgBoot参与同类软件产品市场的竞争。
|
||||
违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。
|
||||
|
||||
总结:在遵循Apache开源协议和开源协议补充条款下,允许商用使用,不会造成侵权行为!
|
||||
解释权归:http://www.jeecg.com
|
||||
|
||||
548
README-EN.md
Normal file
548
README-EN.md
Normal file
@ -0,0 +1,548 @@
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
JEECG BOOT Low Code Development Platform
|
||||
===============
|
||||
|
||||
当前最新版本: 3.7.0(发布日期:2024-06-17)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://www.jeecg.com)
|
||||
[](https://jeecg.blog.csdn.net)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
|
||||
|
||||
Project introduction
|
||||
-----------------------------------
|
||||
|
||||
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
|
||||
|
||||
JeecgBoot is a `low code development platform` based on code `generators`! Front and back end separation architecture SpringBoot2.x, SpringCloud, Ant Design&Vue, Mybatis plus, Shiro, JWT, support for microservices. The powerful code generator makes the front and back end of the code generation, low code development! JeecgBoot leads a new low-code development paradigm (OnlineCoding-> Code Generator -> Manual MERGE) that helps resolve 70% of the duplication in Java projects and makes development more business-focused. Not only can quickly improve efficiency, save research and development costs, but also do not lose flexibility!
|
||||
|
||||
JeecgBoot provides a series of low code modules to make Online development truly zero code: Online form development, online reports, report configuration capabilities, online chart design, large screen design, mobile configuration capabilities, form designer, online design flow, process automation configuration, plug-in capabilities (pluggable) and more!
|
||||
|
||||
|
||||
The purpose of JEECG is: simple functions are implemented by OnlineCoding configuration, so that zero code development; Complex functions are generated by code generator and manually Merge to achieve low code development, which ensures both intelligence and flexibility. The implementation of low code development and support flexible coding at the same time, to solve the current low code products are generally not flexible drawbacks!
|
||||
|
||||
JEECG Business process: Using workflow to implement and extend the task interface for developing and writing business logic, forms provides a variety of solutions: form designer, online configuration form, and coding form. At the same time, the separation design of process and form (loose coupling) is realized, and the flexible configuration of task nodes is supported, which not only ensures the confidentiality of the company's process, but also reduces the workload of developers.
|
||||
|
||||
|
||||
Technical support
|
||||
-----------------------------------
|
||||
|
||||
Problems or bugs in use can be found in [Making on the Issues](https://github.com/jeecgboot/JeecgBoot/issues/new)
|
||||
|
||||
Official Support: http://jeecg.com/doc/help
|
||||
|
||||
|
||||
|
||||
|
||||
Download the source code
|
||||
-----------------------------------
|
||||
- UI(Vue3) SourceCode:https://github.com/jeecgboot/jeecgboot-vue3
|
||||
- APP SourceCode:https://github.com/jeecgboot/jeecg-uniapp
|
||||
|
||||
|
||||
##### Project description
|
||||
|
||||
| Project | description |
|
||||
|--------------------|------------------------|
|
||||
| `jeecg-boot` | SpringBoot background source code (support microservices) |
|
||||
| `jeecgboot-vue3` | Vue3+TS new front-end source code|
|
||||
| `jeecg-uniapp` | [APP development framework, a code multi terminal adaptation, and support APP, small program, H5](https://github.com/jeecgboot/jeecg-uniapp) |
|
||||
| `More` | [Download more source code](http://jeecg.com/download) |
|
||||
|
||||
|
||||
|
||||
|
||||
For the project
|
||||
-----------------------------------
|
||||
Jeecg-Boot low code development platform can be applied in the development of any J2EE project, especially for SAAS projects, enterprise information management system (MIS), internal office system (OA), enterprise resource planning system (ERP), customer relationship management system (CRM), etc. Its semi-intelligent manual Merge development method, Can significantly improve the development efficiency of more than 70%, greatly reduce the development cost.
|
||||
|
||||
|
||||
|
||||
Docker starts the project
|
||||
-----------------------------------
|
||||
|
||||
- [Docker starts the monomer background](https://help.jeecg.com/java/setup/docker/up.html)
|
||||
- [Docker starts the front-end](http://help.jeecg.com/publish/docker.html)
|
||||
- [Docker starts the micro-service background](https://help.jeecg.com/java/springcloud/docker.html)
|
||||
- [ChatGPT AI Config](https://help.jeecg.com/java/chatgpt.html)
|
||||
|
||||
|
||||
|
||||
Technical documentation
|
||||
-----------------------------------
|
||||
|
||||
- Website: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
- Doc: [http://help.jeecg.com](http://help.jeecg.com)
|
||||
- Newbie guide: [Quick start](http://www.jeecg.com/doc/quickstart) | [video](https://space.bilibili.com/454617261/channel/series) | [Q&A ](http://www.jeecg.com/doc/qa) | [help](http://jeecg.com/doc/help) | [1 minute experience](https://my.oschina.net/jeecg/blog/3083313)
|
||||
- Microservice Development: [Monomer upgrade to microservice](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
|
||||
- QQ group : ⑨808791225、⑧825232878、⑦791696430、⑥730954414(full)、683903138(full)、⑤860162132(full)、④774126647(full)、③816531124(full)、②769925425(full)、①284271917(full)
|
||||
- Demo : [OnlineDemo](http://boot3.jeecg.com) | [APP](http://jeecg.com/appIndex)
|
||||
> [please click obtain account password to obtain](http://jeecg.com/doc/demo)
|
||||
|
||||
|
||||
Star charts
|
||||
-----------------------------------
|
||||
|
||||
[](https://star-history.com/#jeecgboot/jeecg-boot)
|
||||
|
||||
|
||||
|
||||
|
||||
Background directory Structure
|
||||
-----------------------------------
|
||||
```
|
||||
project structure
|
||||
├─jeecg-boot-parent
|
||||
│ ├─jeecg-boot-base-core
|
||||
│ ├─jeecg-module-demo
|
||||
│ ├─jeecg-module-system
|
||||
│ │ ├─jeecg-system-biz
|
||||
│ │ ├─jeecg-system-start system (8080)
|
||||
│ │ ├─jeecg-system-api
|
||||
│ │ │ ├─jeecg-system-cloud-api
|
||||
│ │ │ ├─jeecg-system-local-api
|
||||
│ ├─jeecg-server-cloud
|
||||
├─jeecg-cloud-gateway (9999)
|
||||
├─jeecg-cloud-nacos --Nacos(8848)
|
||||
├─jeecg-system-cloud-start --System(7001)
|
||||
├─jeecg-demo-cloud-start --Demo(7002)
|
||||
├─jeecg-visual
|
||||
├─jeecg-cloud-monitor -- (9111)
|
||||
├─jeecg-cloud-xxljob -- (9080)
|
||||
├─jeecg-cloud-sentinel --sentinel (9000)
|
||||
├─jeecg-cloud-test
|
||||
├─jeecg-cloud-test-more
|
||||
├─jeecg-cloud-test-rabbitmq
|
||||
├─jeecg-cloud-test-seata
|
||||
├─jeecg-cloud-test-shardingsphere
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Why JeecgBoot?
|
||||
-----------------------------------
|
||||
* Adopt the latest mainstream front and back separation framework (Springboot+Mybatis+antd), easy to use; Code generator has low dependency, flexible expansion ability, and can quickly realize secondary development;
|
||||
* Support microservices SpringCloud Alibaba(Nacos, Gateway, Sentinel, Skywalking), and provide switching mechanism to support free switching between single and microservices
|
||||
* High development efficiency, using code generator, single table, tree list, one-to-many, one-to-one and other data models, add, delete, change and search function one-key generation, menu configuration directly use;
|
||||
* Code generator provides powerful template mechanism, support custom template, currently provide four sets of style template (single table two sets, tree model one set, one to many three sets)
|
||||
* Code generator is very intelligent, online business modeling, online configuration, WYSIWYG support 23 kinds of controls, a key to generate front and back end code, greatly improve the development efficiency, no longer worry about repeated work.
|
||||
* Low code ability: Online online form (without coding, through online configuration of the form, to achieve the addition, deletion, change and check of the form, support single table, tree, one-to-many, one-to-one model, to achieve everyone can code)
|
||||
* Low code ability: Online online report (without coding, through online configuration, to achieve data report, can quickly extract data, reduce development pressure, to achieve everyone can code)
|
||||
* Low code ability: Online online chart (without coding, through online configuration, to achieve graphs, bar graphs, data reports, etc., support custom layout, to achieve everyone can code)
|
||||
* Complete encapsulation of user, role, menu, organization, data dictionary, online scheduled tasks and other basic functions, support access authorization, button permission, data permission and other functions
|
||||
* Commonly used common package, various tools (scheduled task, SMS interface, email sending,Excel import and export, etc.), basically meeting 80% of project requirements
|
||||
* Easy Excel import and export, support single table export and one-to-many table mode export, generated code with import and export function
|
||||
* Integrated simple report tools, image report and data export is very convenient, can be extremely convenient to generate graphical reports, pdf, excel, word and other reports;
|
||||
* Before and after the separation technology, the page UI style is exquisite, for the commonly used components to do the encapsulation: time, row table control, interception display control, report component, editor and so on
|
||||
* Query filter: query function automatically generated, the background dynamic spell SQL additional query conditions; Supports multiple matching modes (full matching, fuzzy query, included query, and unmatched query).
|
||||
* Data permission (fine data permission control, control to row level, list level, form field level, realize different people see different data, different people operate different fields on the same page
|
||||
* Page verification automatically generated (must be input, digital verification, amount verification, time and space, etc.);
|
||||
* Support SAAS service model and provide SaaS multi-tenant architecture solution.
|
||||
* Distributed file service, integration of minio, Ali OSS and other excellent third parties, to provide convenient file upload and management, but also support local storage.
|
||||
* Mainstream database compatibility, a set of code is fully compatible with Mysql, Postgresql, Oracle, Sqlserver, MariaDB, dream and other mainstream databases.
|
||||
* Integrate workflow flowable and realize only the configuration of flow direction in the page, which can greatly simplify the development of bpm workflow; Using bpm's process designer to draw the flow direction, a workflow is basically complete with a small amount of java code;
|
||||
* Low code ability: online process design, using open source Activiti process engine, to achieve online drawing process, custom form, form attachment, business flow
|
||||
* Multi-data source: its simple way of use, online configuration of data source configuration, convenient to grab data from other data;
|
||||
* Provide single sign-on CAS integration solution, and complete docking code has been provided in the project
|
||||
* Low code ability: form designer, support user custom form layout, support single table, one to many forms, support select, radio, checkbox, textarea, date, popup, list, macro and other controls
|
||||
* Professional interface docking mechanism, unified using restful interface, integrated swagger-ui online interface documentation, Jwt token security verification, convenient client docking
|
||||
* Interface security mechanism, can be refined control interface authorization, very simple to realize different clients only see their own data control
|
||||
* Advanced combination query function, online configuration support primary and sub-table associated query, can save the query history
|
||||
* Provide a variety of system monitoring, real-time tracking system running conditions (monitoring Redis, Tomcat, jvm, server information, request tracking, SQL monitoring)
|
||||
* Message center (support SMS, email, wechat push, etc.)
|
||||
* Integrate Websocket message notification mechanism
|
||||
* Excellent mobile adaptive effect, providing APP release scheme:
|
||||
* Support multiple languages and provide internationalization solutions;
|
||||
* Data change record log, can record each change of data content, through the version comparison function to view historical changes
|
||||
* The platform UI is powerful and mobile adaptation is implemented
|
||||
* Platform home page style, provide a variety of combination mode, support custom style
|
||||
* Provide easy to use print plug-in, support Google, Firefox, IE11+ and other browsers
|
||||
* Rich sample code, provide a lot of learning case reference
|
||||
* Using maven module development method
|
||||
* Support dynamic menu routing
|
||||
* RBAC (Role-Based Access Control) is used for permission control.
|
||||
* Provide new row edit table JVXETable, easily meet a variety of complex ERP layout, with higher performance, more flexible extension, more powerful functions
|
||||
|
||||
|
||||
|
||||
|
||||
Technical Architecture:
|
||||
-----------------------------------
|
||||
|
||||
#### Development Environment
|
||||
|
||||
- Language: Java 8+ (less than 17)
|
||||
|
||||
- IDE(JAVA) : IDEA (lombok plug-in must be installed)
|
||||
|
||||
- IDE(front-end) : Vscode, WebStorm, IDEA
|
||||
|
||||
- Dependency management: Maven
|
||||
|
||||
- Cache: Redis
|
||||
|
||||
- Database: MySQL5.7 + & Oracle 11 g & Sqlserver2017 [More Databases](https://my.oschina.net/jeecg/blog/4905722)
|
||||
|
||||
|
||||
#### backend
|
||||
|
||||
- Basic framework: Spring Boot 2.6.14
|
||||
|
||||
- Microservice framework: Spring Cloud Alibaba 2021.0.1.0
|
||||
|
||||
- Persistence layer framework: MybatisPlus 3.5.1
|
||||
|
||||
- Report tool: JimuReport 1.5.8
|
||||
|
||||
- Security framework: Apache Shiro 1.10.0, Jwt 3.11.0
|
||||
|
||||
- Microservice technology stack: Spring Cloud Alibaba, Nacos, Gateway, Sentinel, Skywalking
|
||||
|
||||
- Database connection pool: Alibaba Druid 1.1.22
|
||||
|
||||
- Log printing: logback
|
||||
|
||||
- Others: autopoi, fastjson, poi, Swagger-ui, quartz, lombok (simplified code), etc.
|
||||
|
||||
|
||||
#### The front end
|
||||
|
||||
- TechnologyStack:`Vue3.0+TypeScript+Vite+AntDesignVue+pinia+echarts`
|
||||
|
||||
#### Support library
|
||||
|
||||
| database | support |
|
||||
| --- | --- |
|
||||
| MySQL | √ |
|
||||
| Oracle11g | √ |
|
||||
| Sqlserver2017 | √ |
|
||||
| PostgreSQL | √ |
|
||||
| MariaDB | √ |
|
||||
| 达梦、人大金仓 | √ |
|
||||
|
||||
|
||||
|
||||
## Microservice solutions
|
||||
|
||||
|
||||
1. Service registration and discovery Nacos √
|
||||
|
||||
2. Nacos √
|
||||
|
||||
3. Route gateway gateway(Three loading modes) √
|
||||
|
||||
4. Distributed http feign √
|
||||
|
||||
5. fuse degrade current limiting Sentinel √
|
||||
|
||||
6. Distributed files Minio and Alioss √
|
||||
|
||||
7. Unified permission control
|
||||
|
||||
8. Service monitoring SpringBootAdmin√
|
||||
|
||||
9. link tracking Skywalking [reference document](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
|
||||
10. Messaging middleware RabbitMQ √
|
||||
|
||||
11. Distributed task xxl-job √
|
||||
|
||||
12. Distributed Transaction Seata
|
||||
|
||||
13. Distributed log elk + kafka
|
||||
|
||||
14. Support docker-compose, k8s, jenkins
|
||||
|
||||
15. CAS SSO √
|
||||
|
||||
16. Route traffic limiting √
|
||||
|
||||
|
||||
#### Microservice architecture diagram
|
||||

|
||||
|
||||
### Jeecg Boot product functionality blueprint
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
### Function module
|
||||
```
|
||||
├─系统管理
|
||||
│ ├─用户管理
|
||||
│ ├─角色管理
|
||||
│ ├─菜单管理
|
||||
│ ├─权限设置(支持按钮权限、数据权限)
|
||||
│ ├─表单权限(控制字段禁用、隐藏)
|
||||
│ ├─部门管理
|
||||
│ ├─我的部门(二级管理员)
|
||||
│ └─字典管理
|
||||
│ └─分类字典
|
||||
│ └─系统公告
|
||||
│ └─职务管理
|
||||
│ └─通讯录
|
||||
│ └─多租户管理
|
||||
├─消息中心
|
||||
│ ├─消息管理
|
||||
│ ├─模板管理
|
||||
├─代码生成器(低代码)
|
||||
│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
|
||||
│ ├─代码生成器模板(提供4套模板,分别支持单表和一对多模型,不同风格选择)
|
||||
│ ├─代码生成器模板(生成代码,自带excel导入导出)
|
||||
│ ├─查询过滤器(查询逻辑无需编码,系统根据页面配置自动生成)
|
||||
│ ├─高级查询器(弹窗自动组合查询条件)
|
||||
│ ├─Excel导入导出工具集成(支持单表,一对多 导入导出)
|
||||
│ ├─平台移动自适应支持
|
||||
├─系统监控
|
||||
│ ├─Gateway路由网关
|
||||
│ ├─性能扫描监控
|
||||
│ │ ├─监控 Redis
|
||||
│ │ ├─Tomcat
|
||||
│ │ ├─jvm
|
||||
│ │ ├─服务器信息
|
||||
│ │ ├─请求追踪
|
||||
│ │ ├─磁盘监控
|
||||
│ ├─定时任务
|
||||
│ ├─系统日志
|
||||
│ ├─消息中心(支持短信、邮件、微信推送等等)
|
||||
│ ├─数据日志(记录数据快照,可对比快照,查看数据变更情况)
|
||||
│ ├─系统通知
|
||||
│ ├─SQL监控
|
||||
│ ├─swagger-ui(在线接口文档)
|
||||
│─报表示例
|
||||
│ ├─曲线图
|
||||
│ └─饼状图
|
||||
│ └─柱状图
|
||||
│ └─折线图
|
||||
│ └─面积图
|
||||
│ └─雷达图
|
||||
│ └─仪表图
|
||||
│ └─进度条
|
||||
│ └─排名列表
|
||||
│ └─等等
|
||||
│─大屏模板
|
||||
│ ├─作战指挥中心大屏
|
||||
│ └─物流服务中心大屏
|
||||
│─常用示例
|
||||
│ ├─自定义组件
|
||||
│ ├─对象存储(对接阿里云)
|
||||
│ ├─JVXETable示例(各种复杂ERP布局示例)
|
||||
│ ├─单表模型例子
|
||||
│ └─一对多模型例子
|
||||
│ └─打印例子
|
||||
│ └─一对多TAB例子
|
||||
│ └─内嵌table例子
|
||||
│ └─常用选择组件
|
||||
│ └─异步树table
|
||||
│ └─接口模拟测试
|
||||
│ └─表格合计示例
|
||||
│ └─异步树列表示例
|
||||
│ └─一对多JEditable
|
||||
│ └─JEditable组件示例
|
||||
│ └─图片拖拽排序
|
||||
│ └─图片翻页
|
||||
│ └─图片预览
|
||||
│ └─PDF预览
|
||||
│ └─分屏功能
|
||||
│─封装通用组件
|
||||
│ ├─行编辑表格JEditableTable
|
||||
│ └─省略显示组件
|
||||
│ └─时间控件
|
||||
│ └─高级查询
|
||||
│ └─用户选择组件
|
||||
│ └─报表组件封装
|
||||
│ └─字典组件
|
||||
│ └─下拉多选组件
|
||||
│ └─选人组件
|
||||
│ └─选部门组件
|
||||
│ └─通过部门选人组件
|
||||
│ └─封装曲线、柱状图、饼状图、折线图等等报表的组件(经过封装,使用简单)
|
||||
│ └─在线code编辑器
|
||||
│ └─上传文件组件
|
||||
│ └─验证码组件
|
||||
│ └─树列表组件
|
||||
│ └─表单禁用组件
|
||||
│ └─等等
|
||||
│─更多页面模板
|
||||
│ ├─各种高级表单
|
||||
│ ├─各种列表效果
|
||||
│ └─结果页面
|
||||
│ └─异常页面
|
||||
│ └─个人页面
|
||||
├─高级功能
|
||||
│ ├─系统编码规则
|
||||
│ ├─提供单点登录CAS集成方案
|
||||
│ ├─提供APP发布方案
|
||||
│ ├─集成Websocket消息通知机制
|
||||
├─Online在线开发(低代码)
|
||||
│ ├─Online在线表单 - 功能已开放
|
||||
│ ├─Online代码生成器 - 功能已开放
|
||||
│ ├─Online在线报表 - 功能已开放
|
||||
│ ├─Online在线图表(未开源)
|
||||
│ ├─Online图表模板配置(未开源)
|
||||
│ ├─Online布局设计(未开源)
|
||||
│ ├─多数据源管理 - 功能已开放
|
||||
├─积木报表设计器(低代码)
|
||||
│ ├─打印设计器
|
||||
│ ├─数据报表设计
|
||||
│ ├─图形报表设计(支持echart)
|
||||
│ ├─大屏设计器(未开源)
|
||||
│─流程模块功能 (未开源)
|
||||
│ ├─流程设计器
|
||||
│ ├─表单设计器
|
||||
├─大屏设计器
|
||||
├─门户设计/仪表盘设计器
|
||||
│ └─我的任务
|
||||
│ └─历史流程
|
||||
│ └─历史流程
|
||||
│ └─流程实例管理
|
||||
│ └─流程监听管理
|
||||
│ └─流程表达式
|
||||
│ └─我发起的流程
|
||||
│ └─我的抄送
|
||||
│ └─流程委派、抄送、跳转
|
||||
│ └─。。。
|
||||
│─OA办公组件 (未开源)
|
||||
│ ├─更多功能
|
||||
│ └─。。。
|
||||
└─其他模块
|
||||
└─更多功能开发中。。
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Effect of system
|
||||
|
||||
##### ChatGPT AI Dialog
|
||||
> Go to the JeecgBoot background home page and click "AI Assistant" in the middle of the right side of the home page. The AI Assistant dialog screen is displayed.
|
||||

|
||||
|
||||
|
||||
##### PC
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### interactive
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
##### process Designer
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### min process
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### dashboard Designer
|
||||

|
||||
|
||||

|
||||
|
||||
##### report Designer
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### form Designer
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### bigscreen Designer
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### uniapp
|
||||

|
||||
|
||||

|
||||
|
||||
##### low app
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### app
|
||||

|
||||

|
||||
|
||||
##### PAD
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
##### chart
|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
##### swagger
|
||||

|
||||

|
||||
|
||||
|
||||
## donation
|
||||
|
||||
If so, buy the author a cup of coffee ☺
|
||||
|
||||

|
||||
232
README.md
232
README.md
@ -1,19 +1,14 @@
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
JEECG BOOT 低代码开发平台
|
||||
JeecgBoot 低代码开发平台
|
||||
===============
|
||||
|
||||
当前最新版本: 3.4.2(发布日期:2022-09-22)
|
||||
当前最新版本: 3.7.0(发布日期:2024-06-17)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://www.jeecg.com)
|
||||
[](https://my.oschina.net/jeecg)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](http://jeecg.com/aboutusIndex)
|
||||
[](https://jeecg.blog.csdn.net)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
@ -24,9 +19,9 @@ JEECG BOOT 低代码开发平台
|
||||
|
||||
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
|
||||
|
||||
JeecgBoot 是一款基于代码生成器的`低代码开发平台`!前后端分离架构 SpringBoot2.x,SpringCloud,Ant Design&Vue,Mybatis-plus,Shiro,JWT,支持微服务。强大的代码生成器让前后端代码一键生成,实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE), 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
|
||||
JeecgBoot 是一款基于代码生成器的`低代码开发平台`!前后端分离架构 SpringBoot2.x和3.x,SpringCloud,Ant Design&Vue,Mybatis-plus,Shiro,JWT,支持微服务。强大的代码生成器让前后端代码一键生成,实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE), 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
|
||||
|
||||
JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零代码`:Online表单开发、Online报表、报表配置能力、在线图表设计、大屏设计、移动配置能力、表单设计器、在线设计流程、流程自动化配置、插件能力(可插拔)等等!
|
||||
JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零代码`:Online表单开发、Online报表、报表配置能力、在线图表设计、仪表盘设计、大屏设计、移动配置能力、表单设计器、在线设计流程、流程自动化配置、插件能力(可插拔)等等!
|
||||
|
||||
|
||||
`JEECG宗旨是:` 简单功能由OnlineCoding配置实现,做到`零代码开发`;复杂功能由代码生成器生成进行手工Merge 实现`低代码开发`,既保证了`智能`又兼顾`灵活`;实现了低代码开发的同时又支持灵活编码,解决了当前低代码产品普遍不灵活的弊端!
|
||||
@ -34,50 +29,67 @@ JeecgBoot 提供了一系列`低代码模块`,实现在线开发`真正的零
|
||||
`JEECG业务流程:` 采用工作流来实现、扩展出任务接口,供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单、编码表单。同时实现了流程与表单的分离设计(松耦合)、并支持任务节点灵活配置,既保证了公司流程的保密性,又减少了开发人员的工作量。
|
||||
|
||||
|
||||
项目源码
|
||||
适用项目
|
||||
-----------------------------------
|
||||
| 仓库 |前端 Vue3版 | 前端 Vue2版 | 后端源码 |
|
||||
|-|-|-|-|
|
||||
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [ant-design-vue-jeecg](https://github.com/jeecgboot/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/ant-design-vue-jeecg) | [jeecg-boot](https://gitee.com/jeecg/jeecg-boot) |
|
||||
Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,支持信创国产化(默认适配达梦和人大金仓)。尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。
|
||||
|
||||
|
||||
##### 项目说明
|
||||
源码下载
|
||||
-----------------------------------
|
||||
- 前端源码地址:https://github.com/jeecgboot/jeecgboot-vue3
|
||||
- APP源码地址:https://github.com/jeecgboot/jeecg-uniapp
|
||||
|
||||
|
||||
#### 项目说明
|
||||
|
||||
| 项目名 | 说明 |
|
||||
|--------------------|------------------------|
|
||||
| `jeecg-boot` | SpringBoot后台源码(支持微服务) |
|
||||
| `ant-design-vue-jeecg` |Vue2版前端源码 |
|
||||
| `jeecgboot-vue3` | Vue3+Ts版前端源码 |
|
||||
| `jeecg-boot-starter` | stater依赖项目单独维护 [下载地址](https://gitee.com/jeecg/jeecg-boot-starter) |
|
||||
| `jeecg-boot` | 后端源码JAVA(SpringBoot微服务架构) |
|
||||
| `jeecgboot-vue3` | 前端源码VUE3(vue3+vite5+ts最新技术栈) |
|
||||
| `jeecg-uniapp` | APP框架,一份代码多终端适配,支持APP、小程序、H5 |
|
||||
|
||||
|
||||
|
||||
##### Star走势图
|
||||

|
||||
|
||||
适用项目
|
||||
技术支持
|
||||
-----------------------------------
|
||||
Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。
|
||||
|
||||
关闭gitee的issue通道,使用中遇到问题或者BUG可以在 [Github上提Issues](https://github.com/jeecgboot/JeecgBoot/issues/new)
|
||||
|
||||
|
||||
快速启动项目
|
||||
-----------------------------------
|
||||
|
||||
- [前端项目快速启动](http://help.jeecg.com/setup/startup.html)
|
||||
- [通过IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup.html)
|
||||
|
||||
|
||||
|
||||
Docker启动项目
|
||||
-----------------------------------
|
||||
|
||||
- [Docker启动前端](http://help.jeecg.com/publish/docker.html)
|
||||
- [Docker启动后台](https://help.jeecg.com/java/setup/docker/up.html)
|
||||
|
||||
|
||||
微服务方式启动
|
||||
-----------------------------------
|
||||
|
||||
- [单体快速切换微服务](https://help.jeecg.com/java/springcloud/switchcloud/monomer.html)
|
||||
- [Docker启动微服务后台](https://help.jeecg.com/java/springcloud/docker.html)
|
||||
|
||||
|
||||
技术文档
|
||||
-----------------------------------
|
||||
|
||||
- 技术官网: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
|
||||
- 在线演示 : [Vue2版本](http://boot.jeecg.com) | [Vue3版本](http://boot3.jeecg.com)
|
||||
|
||||
- 开发文档: [主项目文档](http://doc.jeecg.com) | [Vue3文档](http://vue3.jeecg.com)
|
||||
|
||||
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [常见问题 ](http://www.jeecg.com/doc/qa) | [技术支持](http://jeecg.com/doc/help) | [1分钟体验低代码](https://my.oschina.net/jeecg/blog/3083313)
|
||||
|
||||
- 微服务开发: [单体升级为微服务](http://doc.jeecg.com/3043471)
|
||||
|
||||
- QQ交流群 : ⑥730954414、VUE3群683903138、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
|
||||
> ` 提醒:【QQ群是自助服务群,建议给帮助您解决问题的同学发送指定红包,表示感谢!】 `
|
||||
- 产品官网: [http://www.jeecg.com](http://www.jeecg.com)
|
||||
- 开发文档: [https://help.jeecg.com](https://help.jeecg.com)
|
||||
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [常见问题 ](http://www.jeecg.com/doc/qa) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [1分钟低代码体验](https://my.oschina.net/jeecg/blog/3083313)
|
||||
- AI助手配置: https://help.jeecg.com/java/chatgpt.html
|
||||
|
||||
- 在线演示 : [在线演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex)
|
||||
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
|
||||
>
|
||||
- QQ交流群 : ⑨808791225、⑧825232878、⑦791696430(满)、⑥730954414(满)、683903138(满)、⑤860162132(满)、④774126647(满)、③816531124(满)、②769925425(满)、①284271917(满)
|
||||
|
||||
|
||||
|
||||
@ -110,12 +122,6 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
||||
├─jeecg-cloud-test-shardingsphere -- 微服务测试示例(分库分表)
|
||||
```
|
||||
|
||||
Docker启动项目
|
||||
-----------------------------------
|
||||
- [Docker启动单体后台](http://doc.jeecg.com/2043889)
|
||||
- [Docker启动微服务后台](http://doc.jeecg.com/3043472)
|
||||
- [Docker启动Vue3前端](http://vue3.jeecg.com/3028878)
|
||||
- [Docker启动Vue2前端](http://doc.jeecg.com/3043612)
|
||||
|
||||
|
||||
|
||||
@ -140,8 +146,8 @@ Docker启动项目
|
||||
* 17.支持SAAS服务模式,提供SaaS多租户架构方案。
|
||||
* 18.分布式文件服务,集成minio、阿里OSS等优秀的第三方,提供便捷的文件上传与管理,同时也支持本地存储。
|
||||
* 19.主流数据库兼容,一套代码完全兼容Mysql、Postgresql、Oracle、Sqlserver、MariaDB、达梦等主流数据库。
|
||||
* 20.集成工作流activiti,并实现了只需在页面配置流程转向,可极大的简化bpm工作流的开发;用bpm的流程设计器画出了流程走向,一个工作流基本就完成了,只需写很少量的java代码;
|
||||
* 21.低代码能力:在线流程设计,采用开源Activiti流程引擎,实现在线画流程,自定义表单,表单挂靠,业务流转
|
||||
* 20.集成工作流flowable,并实现了只需在页面配置流程转向,可极大的简化bpm工作流的开发;用bpm的流程设计器画出了流程走向,一个工作流基本就完成了,只需写很少量的java代码;
|
||||
* 21.低代码能力:在线流程设计,采用开源flowable流程引擎,实现在线画流程,自定义表单,表单挂靠,业务流转
|
||||
* 22.多数据源:及其简易的使用方式,在线配置数据源配置,便捷的从其他数据抓取数据;
|
||||
* 23.提供单点登录CAS集成方案,项目中已经提供完善的对接代码
|
||||
* 24.低代码能力:表单设计器,支持用户自定义表单布局,支持单表,一对多表单、支持select、radio、checkbox、textarea、date、popup、列表、宏等控件
|
||||
@ -180,20 +186,20 @@ Docker启动项目
|
||||
|
||||
- 缓存:Redis
|
||||
|
||||
- 数据库脚本:MySQL5.7+ & Oracle 11g & Sqlserver2017(其他数据库,[需要自己转](https://my.oschina.net/jeecg/blog/4905722))
|
||||
- 数据库脚本:MySQL5.7+ (其他数据库,[需要自己转](https://my.oschina.net/jeecg/blog/4905722))
|
||||
|
||||
|
||||
#### 后端
|
||||
|
||||
- 基础框架:Spring Boot 2.6.6
|
||||
- 基础框架:Spring Boot 2.6.14
|
||||
|
||||
- 微服务框架: Spring Cloud Alibaba 2021.0.1.0
|
||||
|
||||
- 持久层框架:MybatisPlus 3.5.1
|
||||
|
||||
- 报表工具: JimuReport 1.5.2
|
||||
- 报表工具: JimuReport 1.5.8
|
||||
|
||||
- 安全框架:Apache Shiro 1.8.0,Jwt 3.11.0
|
||||
- 安全框架:Apache Shiro 1.10.0,Jwt 3.11.0
|
||||
|
||||
- 微服务技术栈:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
|
||||
|
||||
@ -206,8 +212,7 @@ Docker启动项目
|
||||
|
||||
#### 前端
|
||||
|
||||
- Vue2版本:`Vue2.6+@vue/cli+AntDesignVue+Viser-vue+Vuex等` [详细查看](https://github.com/jeecgboot/ant-design-vue-jeecg)
|
||||
- Vue3版本:`Vue3.0+TypeScript+Vite+AntDesignVue+pinia+echarts等新方案` [详细查看](https://github.com/jeecgboot/jeecgboot-vue3)
|
||||
- 技术栈:`Vue3.0+TypeScript+Vite+AntDesignVue+pinia+echarts等最新技术栈`
|
||||
|
||||
#### 支持库
|
||||
|
||||
@ -241,7 +246,7 @@ Docker启动项目
|
||||
|
||||
8、服务监控 SpringBootAdmin√
|
||||
|
||||
9、链路跟踪 Skywalking [参考文档](http://doc.jeecg.com/2350293)
|
||||
9、链路跟踪 Skywalking [参考文档](https://help.jeecg.com/java/springcloud/super/skywarking.html)
|
||||
|
||||
10、消息中间件 RabbitMQ √
|
||||
|
||||
@ -388,7 +393,7 @@ Docker启动项目
|
||||
│ ├─数据报表设计
|
||||
│ ├─图形报表设计(支持echart)
|
||||
│ ├─大屏设计器(未开源)
|
||||
│─流程模块功能 (未开源)
|
||||
│─更多商业功能 (未开源)
|
||||
│ ├─流程设计器
|
||||
│ ├─表单设计器
|
||||
├─大屏设计器
|
||||
@ -413,40 +418,43 @@ Docker启动项目
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
系统效果
|
||||
----
|
||||
##### 大屏模板
|
||||

|
||||
|
||||

|
||||
### 系统效果
|
||||
|
||||
##### PC端
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
##### 系统交互
|
||||

|
||||
|
||||
##### 在线接口文档
|
||||

|
||||

|
||||

|
||||
|
||||
##### AI助手
|
||||

|
||||
|
||||
|
||||
##### 报表
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
##### 仪表盘设计器
|
||||

|
||||
|
||||
##### 流程
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
##### 报表设计器
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
##### 手机端
|
||||
@ -456,12 +464,76 @@ Docker启动项目
|
||||
##### PAD端
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
##### 图表示例
|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
##### 在线接口文档
|
||||

|
||||

|
||||
|
||||
|
||||
##### UNIAPP效果
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
##### 大屏设计器
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
##### 流程设计
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
##### 表单设计器
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 捐赠
|
||||
|
||||
如果觉得还不错,请作者喝杯咖啡吧 ☺
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 流程引擎推荐
|
||||
|
||||
大家在使用本开源项目时,如果想进一步集成流程引擎,推荐结合贺波老师的书 [《深入Activiti流程引擎:核心原理与高阶实战》](https://item.m.jd.com/product/13928958.html?gx=RnAomTM2bmCImZxDqYAkVCoIHuIYVqc)
|
||||
|
||||
<img src="https://jeecgos.oss-cn-beijing.aliyuncs.com/files/tuijian20231220161656.png" width="25%" height="auto">
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,102 +0,0 @@
|
||||
-- 系统通知消息支持标星
|
||||
ALTER TABLE `sys_announcement_send`
|
||||
ADD COLUMN `star_flag` varchar(10) NULL COMMENT '标星状态( 1为标星 空/0没有标星)' AFTER `update_time`;
|
||||
|
||||
ALTER TABLE `sys_comment`
|
||||
MODIFY COLUMN `comment_content` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '回复内容' AFTER `comment_id`;
|
||||
|
||||
ALTER TABLE `sys_data_log`
|
||||
ADD COLUMN `type` varchar(20) NULL DEFAULT 'json' COMMENT '类型' AFTER `data_version`;
|
||||
update sys_data_log set type = 'json';
|
||||
|
||||
|
||||
-- vue3单表原生组件菜单
|
||||
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`, `internal_or_external`) VALUES ('1534418199197323265', '1438108197958311537', '单表原生示例', '/one/OneNativeList', 'demo/jeecg/Native/one/OneNativeList', 1, NULL, NULL, 1, NULL, '0', 13.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2022-06-08 14:13:01', 'admin', '2022-06-08 14:13:12', 0, 0, NULL, 0);
|
||||
|
||||
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `type`) VALUES ('1242298510024429569', '提醒方式', 'remindMode', '', 0, 'admin', '2020-03-24 11:53:40', 'admin', '2020-03-24 12:03:22', 0);
|
||||
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES ('1242300779390357505', '1242298510024429569', '短信提醒', '2', '', 2, 1, 'admin', '2020-03-24 12:02:41', 'admin', '2020-03-30 18:21:33');
|
||||
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES ('1242300814383435777', '1242298510024429569', '邮件提醒', '1', '', 1, 1, 'admin', '2020-03-24 12:02:49', 'admin', '2020-03-30 18:21:26');
|
||||
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES ('1242300887343353857', '1242298510024429569', '系统消息', '4', '', 4, 1, 'admin', '2020-03-24 12:03:07', 'admin', '2020-03-30 18:21:43');
|
||||
|
||||
-- 删除vue3作废菜单
|
||||
DELETE from sys_permission where component = 'demo/comp/upload/index';
|
||||
|
||||
-- vue3示例菜单缓存配置修正
|
||||
UPDATE `sys_permission` SET `parent_id` = '1438108187455774722', `name` = '多级菜单', `url` = '/level', `component` = 'layouts/default/index', `is_route` = 1, `component_name` = NULL, `redirect` = '/level/menu1/menu1-1/menu1-1-1', `menu_type` = 0, `perms` = NULL, `perms_type` = '1', `sort_no` = 9.00, `always_show` = 0, `icon` = 'ion:menu-outline', `is_leaf` = 0, `keep_alive` = 0, `hidden` = 0, `hide_tab` = NULL, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = NULL, `update_time` = NULL, `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108220418809857';
|
||||
UPDATE `sys_permission` SET `parent_id` = '1438108220418809857', `name` = 'Menu1', `url` = '/level/menu1', `component` = NULL, `is_route` = 1, `component_name` = NULL, `redirect` = '/level/menu1/menu1-1/menu1-1-1', `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 0.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 0, `keep_alive` = 0, `hidden` = 0, `hide_tab` = NULL, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = NULL, `update_time` = NULL, `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108220523667458';
|
||||
UPDATE `sys_permission` SET `parent_id` = '1438108220418809857', `name` = 'Menu2', `url` = '/level/menu2', `component` = 'demo/level/Menu2', `is_route` = 1, `component_name` = 'Menu2Demo', `redirect` = NULL, `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 1.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 1, `keep_alive` = 1, `hidden` = 0, `hide_tab` = 0, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = 'admin', `update_time` = '2022-09-20 15:24:13', `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108220724994049';
|
||||
UPDATE `sys_permission` SET `parent_id` = '1438108220523667458', `name` = 'Menu1-1', `url` = '/level/menu1/menu1-1', `component` = NULL, `is_route` = 1, `component_name` = NULL, `redirect` = '/level/menu1/menu1-1/menu1-1-1', `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 0.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 0, `keep_alive` = 0, `hidden` = 0, `hide_tab` = NULL, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = NULL, `update_time` = NULL, `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108220896960513';
|
||||
UPDATE `sys_permission` SET `parent_id` = '1438108220896960513', `name` = 'Menu1-1-1', `url` = '/level/menu1/menu1-1/menu1-1-1', `component` = 'demo/level/Menu111', `is_route` = 1, `component_name` = 'Menu111Demo', `redirect` = NULL, `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 0.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 1, `keep_alive` = 1, `hidden` = 0, `hide_tab` = 0, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = 'admin', `update_time` = '2022-09-20 15:24:03', `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108221127647234';
|
||||
UPDATE `sys_permission` SET `parent_id` = '1438108220523667458', `name` = 'Menu1-2', `url` = '/level/menu1/menu1-2', `component` = 'demo/level/Menu12', `is_route` = 1, `component_name` = 'Menu12Demo', `redirect` = NULL, `menu_type` = 1, `perms` = NULL, `perms_type` = '1', `sort_no` = 1.00, `always_show` = 0, `icon` = NULL, `is_leaf` = 1, `keep_alive` = 1, `hidden` = 0, `hide_tab` = 0, `description` = NULL, `create_by` = 'admin', `create_time` = '2021-09-15 19:51:33', `update_by` = 'admin', `update_time` = '2022-09-20 15:24:08', `del_flag` = 0, `rule_flag` = 0, `status` = '1', `internal_or_external` = 0 WHERE `id` = '1438108221270253570';
|
||||
|
||||
|
||||
-- 新增评论表和文件表
|
||||
DROP TABLE IF EXISTS `sys_comment`;
|
||||
CREATE TABLE `sys_comment` (
|
||||
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||||
`table_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '表名',
|
||||
`table_data_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '数据id',
|
||||
`from_user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '来源用户id',
|
||||
`to_user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发送给用户id(允许为空)',
|
||||
`comment_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '评论id(允许为空,不为空时,则为回复)',
|
||||
`comment_content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '回复内容',
|
||||
`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 '更新日期',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_table_data_id`(`table_name`, `table_data_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统评论回复表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
DROP TABLE IF EXISTS `sys_files`;
|
||||
CREATE TABLE `sys_files` (
|
||||
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键id',
|
||||
`file_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名称',
|
||||
`url` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件地址',
|
||||
`file_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文档类型(folder:文件夹 excel:excel doc:word ppt:ppt image:图片 archive:其他文档 video:视频 pdf:pdf)',
|
||||
`store_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件上传类型(temp/本地上传(临时文件) manage/知识库)',
|
||||
`parent_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '父级id',
|
||||
`tenant_id` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '租户id',
|
||||
`file_size` double(13, 2) NULL DEFAULT NULL COMMENT '文件大小(kb)',
|
||||
`iz_folder` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否文件夹(1:是 0:否)',
|
||||
`iz_root_folder` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否为1级文件夹,允许为空 (1:是 )',
|
||||
`iz_star` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否标星(1:是 0:否)',
|
||||
`down_count` int(11) NULL DEFAULT NULL COMMENT '下载次数',
|
||||
`read_count` int(11) NULL DEFAULT NULL COMMENT '阅读次数',
|
||||
`share_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分享链接',
|
||||
`share_perms` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分享权限(1.关闭分享 2.允许所有联系人查看 3.允许任何人查看)',
|
||||
`enable_down` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否允许下载(1:是 0:否)',
|
||||
`enable_updat` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否允许修改(1:是 0:否)',
|
||||
`del_flag` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '删除状态(0-正常,1-删除至回收站)',
|
||||
`create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人登录名称',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建日期',
|
||||
`update_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人登录名称',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新日期',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '知识库-文档管理' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
DROP TABLE IF EXISTS `sys_form_file`;
|
||||
CREATE TABLE `sys_form_file` (
|
||||
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||||
`table_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '表名',
|
||||
`table_data_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '数据id',
|
||||
`file_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '关联文件id',
|
||||
`file_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件类型(text:文本, excel:excel doc:word ppt:ppt image:图片 archive:其他文档 video:视频 pdf:pdf))',
|
||||
`create_by` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人登录名称',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建日期',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_table_form`(`table_name`, `table_data_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
ALTER TABLE `sys_files`
|
||||
MODIFY COLUMN `tenant_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '租户id' AFTER `parent_id`;
|
||||
|
||||
ALTER TABLE `sys_files`
|
||||
ADD INDEX `index_tenant_id`(`tenant_id`),
|
||||
ADD INDEX `index_del_flag`(`del_flag`);
|
||||
|
||||
ALTER TABLE `sys_form_file`
|
||||
ADD INDEX `index_file_id`(`file_id`);
|
||||
|
||||
|
||||
|
||||
45
db/增量SQL/sas升级脚本.sql
Normal file
45
db/增量SQL/sas升级脚本.sql
Normal file
@ -0,0 +1,45 @@
|
||||
CREATE TABLE `oauth2_registered_client` (
|
||||
`id` varchar(100) NOT NULL,
|
||||
`client_id` varchar(100) NOT NULL,
|
||||
`client_id_issued_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`client_secret` varchar(200) DEFAULT NULL,
|
||||
`client_secret_expires_at` timestamp NULL DEFAULT NULL,
|
||||
`client_name` varchar(200) NOT NULL,
|
||||
`client_authentication_methods` varchar(1000) NOT NULL,
|
||||
`authorization_grant_types` varchar(1000) NOT NULL,
|
||||
`redirect_uris` varchar(1000) DEFAULT NULL,
|
||||
`post_logout_redirect_uris` varchar(1000) DEFAULT NULL,
|
||||
`scopes` varchar(1000) NOT NULL,
|
||||
`client_settings` varchar(2000) NOT NULL,
|
||||
`token_settings` varchar(2000) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
INSERT INTO `oauth2_registered_client`
|
||||
(`id`,
|
||||
`client_id`,
|
||||
`client_id_issued_at`,
|
||||
`client_secret`,
|
||||
`client_secret_expires_at`,
|
||||
`client_name`,
|
||||
`client_authentication_methods`,
|
||||
`authorization_grant_types`,
|
||||
`redirect_uris`,
|
||||
`post_logout_redirect_uris`,
|
||||
`scopes`,
|
||||
`client_settings`,
|
||||
`token_settings`)
|
||||
VALUES
|
||||
('3eacac0e-0de9-4727-9a64-6bdd4be2ee1f',
|
||||
'jeecg-client',
|
||||
now(),
|
||||
'secret',
|
||||
null,
|
||||
'3eacac0e-0de9-4727-9a64-6bdd4be2ee1f',
|
||||
'client_secret_basic',
|
||||
'refresh_token,authorization_code,password,app,phone,social',
|
||||
'http://127.0.0.1:8080/jeecg-',
|
||||
'http://127.0.0.1:8080/',
|
||||
'*',
|
||||
'{"@class":"java.util.Collections$UnmodifiableMap","settings.client.require-proof-key":false,"settings.client.require-authorization-consent":true}',
|
||||
'{"@class":"java.util.Collections$UnmodifiableMap","settings.token.reuse-refresh-tokens":true,"settings.token.id-token-signature-algorithm":["org.springframework.security.oauth2.jose.jws.SignatureAlgorithm","RS256"],"settings.token.access-token-time-to-live":["java.time.Duration",300000.000000000],"settings.token.access-token-format":{"@class":"org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat","value":"self-contained"},"settings.token.refresh-token-time-to-live":["java.time.Duration",3600.000000000],"settings.token.authorization-code-time-to-live":["java.time.Duration",300000.000000000],"settings.token.device-code-time-to-live":["java.time.Duration",300000.000000000]}');
|
||||
@ -1,10 +0,0 @@
|
||||
版本升级方法?
|
||||
|
||||
JeecgBoot属于平台级产品,每次升级改动内容较多,目前做不到平滑升级。
|
||||
|
||||
这里给用户的升级建议是这样的:
|
||||
1.代码升级 => 本地版本通过svn或者git做好主干,在分支上做业务开发,jeecg每次版本发布,可以手工覆盖主干的代码,对比代码进行提交;
|
||||
2.数据库升级 => 针对数据库我们每次发布会提供增量升级SQL,可以通过增量SQL实现数据库的升级。
|
||||
3.兼容问题 => 每次版本发布会针对不兼容地方标注说明,需要手工修改不兼容的代码。
|
||||
|
||||
注意: 升级sql目前只提供mysql版本,执行完脚步后,新菜单需要手工进行角色授权,刷新首页才会出现。
|
||||
15
db/版本升级说明.md
Normal file
15
db/版本升级说明.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 版本升级方法
|
||||
|
||||
> JeecgBoot属于平台级产品,每次升级改动较大,目前做不到平滑升级。
|
||||
|
||||
### 增量升级方案
|
||||
#### 1.代码合并
|
||||
本地通过svn或git做好主干,在分支上做业务开发,jeecg每次版本发布,可以手工覆盖主干的代码,对比合并代码;
|
||||
|
||||
#### 2.数据库升级
|
||||
- 从3.6.2+版本增加flyway自动升级数据库机制,支持 mysql5.7、mysql8;
|
||||
- 其他库请手工执行SQL, 目录: `jeecg-module-system\jeecg-system-start\src\main\resources\flyway\sql\mysql`
|
||||
> 注意: 升级sql只提供mysql版本;如果有权限升级, 还需要手工角色授权,退出重新登录才好使。
|
||||
|
||||
#### 3.兼容问题
|
||||
每次发版,会针对不兼容地方重点说明。
|
||||
@ -19,6 +19,8 @@ services:
|
||||
--default-authentication-plugin=caching_sha2_password
|
||||
ports:
|
||||
- 3306:3306
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-redis:
|
||||
image: redis:5.0
|
||||
@ -27,6 +29,8 @@ services:
|
||||
restart: always
|
||||
hostname: jeecg-boot-redis
|
||||
container_name: jeecg-boot-redis
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
jeecg-boot-system:
|
||||
build:
|
||||
@ -39,4 +43,10 @@ services:
|
||||
image: jeecg-boot-system
|
||||
hostname: jeecg-boot-system
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 8080:8080
|
||||
networks:
|
||||
- jeecg-boot
|
||||
|
||||
networks:
|
||||
jeecg-boot:
|
||||
name: jeecg_boot
|
||||
|
||||
@ -2,24 +2,67 @@
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.4.2</version>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<version>3.7.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jeecg-boot-base-core</artifactId>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>3.1.5</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>aliyun</id>
|
||||
<name>aliyun Repository</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jeecg</id>
|
||||
<name>jeecg Repository</name>
|
||||
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jeecg-snapshots</id>
|
||||
<name>jeecg-snapshots Repository</name>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!--jeecg-tools-->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-common</artifactId>
|
||||
<artifactId>jeecg-boot-common3</artifactId>
|
||||
</dependency>
|
||||
<!--集成springmvc框架并实现自动配置 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
<!-- websocket -->
|
||||
<dependency>
|
||||
@ -53,7 +96,7 @@
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons.version}</version>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
@ -76,14 +119,14 @@
|
||||
<!-- druid -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 动态数据源 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||
<version>${dynamic-datasource-spring-boot-starter.version}</version>
|
||||
</dependency>
|
||||
|
||||
@ -116,6 +159,25 @@
|
||||
<version>${postgresql.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!--人大金仓驱动 版本号V008R006C005B0013 -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<artifactId>kingbase8</artifactId>
|
||||
<version>${kingbase8.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!--达梦数据库驱动 版本号1-3-26-2023.07.26-197096-20046-ENT -->
|
||||
<dependency>
|
||||
<groupId>com.dameng</groupId>
|
||||
<artifactId>Dm8JdbcDriver18</artifactId>
|
||||
<version>${dm8.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.dameng</groupId>
|
||||
<artifactId>DmDialect-for-hibernate5.0</artifactId>
|
||||
<version>${dm8.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Quartz定时任务 -->
|
||||
<dependency>
|
||||
@ -130,34 +192,30 @@
|
||||
<version>${java-jwt.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--shiro-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring-boot-starter</artifactId>
|
||||
<version>${shiro.version}</version>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
|
||||
</dependency>
|
||||
<!-- shiro-redis -->
|
||||
<dependency>
|
||||
<groupId>org.crazycake</groupId>
|
||||
<artifactId>shiro-redis</artifactId>
|
||||
<version>${shiro-redis.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||
</dependency>
|
||||
<!-- 添加spring security cas支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-cas</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- knife4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>${knife4j-spring-boot-starter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 代码生成器 -->
|
||||
<!-- 如下载失败,请参考此文档 http://doc.jeecg.com/2043876 -->
|
||||
<!-- 如下载失败,请参考此文档 https://help.jeecg.com/java/setup/maven.html -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>codegenerate</artifactId>
|
||||
@ -166,7 +224,7 @@
|
||||
|
||||
<!-- AutoPoi Excel工具类-->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework</groupId>
|
||||
<groupId>org.jeecgframework.boot3</groupId>
|
||||
<artifactId>autopoi-web</artifactId>
|
||||
<version>${autopoi-web.version}</version>
|
||||
<exclusions>
|
||||
@ -174,8 +232,18 @@
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>xercesImpl</artifactId>
|
||||
<groupId>xerces</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xerces</groupId>
|
||||
<artifactId>xercesImpl</artifactId>
|
||||
<version>2.12.2</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- mini文件存储服务 -->
|
||||
<dependency>
|
||||
@ -199,6 +267,16 @@
|
||||
<dependency>
|
||||
<groupId>com.xkcoding.justauth</groupId>
|
||||
<artifactId>justauth-spring-boot-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
@ -209,6 +287,24 @@
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-kotlin</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
</dependency>
|
||||
<!--加载hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- chatgpt -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-starter3-chatgpt</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,21 @@
|
||||
package org.apache.shiro;
|
||||
|
||||
import org.apache.shiro.subject.Subject;
|
||||
|
||||
/**
|
||||
* 兼容处理Online功能使用处理,请勿修改
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/4/29 14:05
|
||||
*/
|
||||
public class SecurityUtils {
|
||||
|
||||
|
||||
public static Subject getSubject() {
|
||||
return new Subject() {
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return Subject.super.getPrincipal();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package org.apache.shiro.subject;
|
||||
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
|
||||
/**
|
||||
* 兼容处理Online功能使用处理,请勿修改
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/4/29 14:18
|
||||
*/
|
||||
public interface Subject {
|
||||
default Object getPrincipal() {
|
||||
return SecureUtil.currentUser();
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.api;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.jeecg.common.system.vo.*;
|
||||
|
||||
import java.util.List;
|
||||
@ -18,14 +19,21 @@ public interface CommonAPI {
|
||||
* @return
|
||||
*/
|
||||
Set<String> queryUserRoles(String username);
|
||||
|
||||
/**
|
||||
* 1查询用户角色信息
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Set<String> queryUserRolesById(String userId);
|
||||
|
||||
|
||||
/**
|
||||
* 2查询用户权限信息
|
||||
* @param username
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Set<String> queryUserAuths(String username);
|
||||
Set<String> queryUserAuths(String userId);
|
||||
|
||||
/**
|
||||
* 3根据 id 查询数据库中存储的 DynamicDataSourceModel
|
||||
@ -49,6 +57,20 @@ public interface CommonAPI {
|
||||
* @return
|
||||
*/
|
||||
public LoginUser getUserByName(String username);
|
||||
|
||||
/**
|
||||
* 5根据用户账号查询用户Id
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
public String getUserIdByName(String username);
|
||||
|
||||
/**
|
||||
* 5根据用户手机号查询用户信息
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
public LoginUser getUserByPhone(String phone);
|
||||
|
||||
|
||||
/**
|
||||
@ -102,12 +124,12 @@ public interface CommonAPI {
|
||||
|
||||
/**
|
||||
* 13获取表数据字典
|
||||
* @param table
|
||||
* @param tableFilterSql
|
||||
* @param text
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
List<DictModel> queryTableDictItemsByCode(String table, String text, String code);
|
||||
List<DictModel> queryTableDictItemsByCode(String tableFilterSql, String text, String code);
|
||||
|
||||
/**
|
||||
* 14 普通字典的翻译,根据多个dictCode和多条数据,多个以逗号分割
|
||||
@ -117,14 +139,44 @@ public interface CommonAPI {
|
||||
*/
|
||||
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
|
||||
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
/**
|
||||
* 15 字典表的 翻译,可批量
|
||||
* @param table
|
||||
* @param text
|
||||
* @param code
|
||||
* @param keys 多个用逗号分割
|
||||
* @param dataSource 数据源
|
||||
* @return
|
||||
*/
|
||||
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys);
|
||||
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys, String dataSource);
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
|
||||
/**
|
||||
* 登录加载系统字典
|
||||
* @return
|
||||
*/
|
||||
Map<String,List<DictModel>> queryAllDictItems();
|
||||
|
||||
/**
|
||||
* 查询SysDepart集合
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
List<SysDepartModel> queryUserDeparts(String userId);
|
||||
|
||||
/**
|
||||
* 根据用户名设置部门ID
|
||||
* @param username
|
||||
* @param orgCode
|
||||
*/
|
||||
void updateUserDepart(String username,String orgCode,Integer loginTenantId);
|
||||
|
||||
/**
|
||||
* 设置登录租户
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
JSONObject setLoginTenant(String username);
|
||||
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ public class DataLogDTO {
|
||||
|
||||
private String type;
|
||||
|
||||
private String createName;
|
||||
|
||||
public DataLogDTO(){
|
||||
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ package org.jeecg.common.api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.api.dto;
|
||||
import lombok.Data;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
@ -50,6 +51,16 @@ public class LogDTO implements Serializable {
|
||||
/**操作人用户账户*/
|
||||
private String userid;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private Integer tenantId;
|
||||
|
||||
/**
|
||||
* 客户终端类型 pc:电脑端 app:手机端 h5:移动网页端
|
||||
*/
|
||||
private String clientType;
|
||||
|
||||
public LogDTO(){
|
||||
|
||||
}
|
||||
|
||||
@ -30,6 +30,13 @@ public class OnlineAuthDTO implements Serializable {
|
||||
*/
|
||||
private String onlineFormUrl;
|
||||
|
||||
//update-begin---author:chenrui ---date:20240123 for:[QQYUN-7992]【online】工单申请下的online表单,未配置online表单开发菜单,操作报错无权限------------
|
||||
/**
|
||||
* online工单的地址
|
||||
*/
|
||||
private String onlineWorkOrderUrl;
|
||||
//update-end---author:chenrui ---date:20240123 for:[QQYUN-7992]【online】工单申请下的online表单,未配置online表单开发菜单,操作报错无权限------------
|
||||
|
||||
public OnlineAuthDTO(){
|
||||
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import lombok.Data;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 普通消息
|
||||
@ -43,14 +43,7 @@ public class MessageDTO implements Serializable {
|
||||
* 消息类型 1:消息 2:系统消息
|
||||
*/
|
||||
protected String category;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
//update-begin---author:taoyan ---date:20220705 for:支持自定义推送类型,邮件、钉钉、企业微信、系统消息-----------
|
||||
|
||||
/**
|
||||
* 模板消息对应的模板编码
|
||||
*/
|
||||
protected String templateCode;
|
||||
|
||||
/**
|
||||
* 消息类型:org.jeecg.common.constant.enums.MessageTypeEnum
|
||||
* XT("system", "系统消息")
|
||||
@ -60,18 +53,38 @@ public class MessageDTO implements Serializable {
|
||||
*/
|
||||
protected String type;
|
||||
|
||||
|
||||
//---【推送模板相关参数】-------------------------------------------------------------
|
||||
/**
|
||||
* 是否发送Markdown格式的消息
|
||||
*/
|
||||
protected boolean isMarkdown;
|
||||
|
||||
/**
|
||||
* 模板消息对应的模板编码
|
||||
*/
|
||||
protected String templateCode;
|
||||
/**
|
||||
* 解析模板内容 对应的数据
|
||||
*/
|
||||
protected Map<String, Object> data;
|
||||
//update-end---author:taoyan ---date::20220705 for:支持自定义推送类型,邮件、钉钉、企业微信、系统消息-----------
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
//---【推送模板相关参数】-------------------------------------------------------------
|
||||
|
||||
//---【邮件相关参数】-------------------------------------------------------------
|
||||
/**
|
||||
* 邮件抄送人
|
||||
*/
|
||||
private String copyToUser;
|
||||
|
||||
/**
|
||||
* 邮件推送地址
|
||||
*/
|
||||
protected Set<String> toEmailList;
|
||||
|
||||
/**
|
||||
* 邮件抄送地址
|
||||
*/
|
||||
protected Set<String> ccEmailList;
|
||||
//---【邮件相关参数】-------------------------------------------------------------
|
||||
|
||||
public MessageDTO(){
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
package org.jeecg.common.api.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
||||
@ -15,7 +14,7 @@ import java.io.Serializable;
|
||||
* @date 2019年1月19日
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value="接口返回对象", description="接口返回对象")
|
||||
@Schema(description="接口返回对象")
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@ -23,31 +22,31 @@ public class Result<T> implements Serializable {
|
||||
/**
|
||||
* 成功标志
|
||||
*/
|
||||
@ApiModelProperty(value = "成功标志")
|
||||
@Schema(description = "成功标志")
|
||||
private boolean success = true;
|
||||
|
||||
/**
|
||||
* 返回处理消息
|
||||
*/
|
||||
@ApiModelProperty(value = "返回处理消息")
|
||||
@Schema(description = "返回处理消息")
|
||||
private String message = "";
|
||||
|
||||
/**
|
||||
* 返回代码
|
||||
*/
|
||||
@ApiModelProperty(value = "返回代码")
|
||||
@Schema(description = "返回代码")
|
||||
private Integer code = 0;
|
||||
|
||||
/**
|
||||
* 返回数据对象 data
|
||||
*/
|
||||
@ApiModelProperty(value = "返回数据对象")
|
||||
@Schema(description = "返回数据对象")
|
||||
private T result;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
@ApiModelProperty(value = "时间戳")
|
||||
@Schema(description = "时间戳")
|
||||
private long timestamp = System.currentTimeMillis();
|
||||
|
||||
public Result() {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.aspect;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@ -15,19 +16,21 @@ import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.enums.ModuleType;
|
||||
import org.jeecg.common.constant.enums.OperateTypeEnum;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.IpUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
|
||||
@ -100,7 +103,7 @@ public class AutoLogAspect {
|
||||
//设置IP地址
|
||||
dto.setIp(IpUtils.getIpAddr(request));
|
||||
//获取登录用户信息
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser sysUser = SecureUtil.currentUser();
|
||||
if(sysUser!=null){
|
||||
dto.setUserid(sysUser.getUsername());
|
||||
dto.setUsername(sysUser.getRealname());
|
||||
@ -158,6 +161,9 @@ public class AutoLogAspect {
|
||||
if(value!=null && value.toString().length()>length){
|
||||
return false;
|
||||
}
|
||||
if(value instanceof MultipartFile){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@ -169,7 +175,7 @@ public class AutoLogAspect {
|
||||
// 请求的方法参数值
|
||||
Object[] args = joinPoint.getArgs();
|
||||
// 请求的方法参数名称
|
||||
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
|
||||
StandardReflectionParameterNameDiscoverer u=new StandardReflectionParameterNameDiscoverer();
|
||||
String[] paramNames = u.getParameterNames(method);
|
||||
if (args != null && paramNames != null) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
|
||||
@ -52,7 +52,9 @@ public class DictAspect {
|
||||
/**
|
||||
* 定义切点Pointcut
|
||||
*/
|
||||
@Pointcut("execution(public * org.jeecg.modules..*.*Controller.*(..)) || @annotation(org.jeecg.common.aspect.annotation.AutoDict)")
|
||||
@Pointcut("(@within(org.springframework.web.bind.annotation.RestController) || " +
|
||||
"@within(org.springframework.stereotype.Controller) || @annotation(org.jeecg.common.aspect.annotation.AutoDict)) " +
|
||||
"&& execution(public org.jeecg.common.api.vo.Result org.jeecg..*.*(..))")
|
||||
public void excudeService() {
|
||||
}
|
||||
|
||||
@ -92,7 +94,8 @@ public class DictAspect {
|
||||
* @param result
|
||||
*/
|
||||
private Object parseDictText(Object result) {
|
||||
if (result instanceof Result) {
|
||||
//if (result instanceof Result) {
|
||||
if (true) {
|
||||
if (((Result) result).getResult() instanceof IPage) {
|
||||
List<JSONObject> items = new ArrayList<>();
|
||||
|
||||
@ -140,11 +143,15 @@ public class DictAspect {
|
||||
String code = field.getAnnotation(Dict.class).dicCode();
|
||||
String text = field.getAnnotation(Dict.class).dicText();
|
||||
String table = field.getAnnotation(Dict.class).dictTable();
|
||||
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
String dataSource = field.getAnnotation(Dict.class).ds();
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
List<String> dataList;
|
||||
String dictCode = code;
|
||||
if (!StringUtils.isEmpty(table)) {
|
||||
dictCode = String.format("%s,%s,%s", table, text, code);
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
}
|
||||
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
|
||||
@ -169,10 +176,15 @@ public class DictAspect {
|
||||
String code = field.getAnnotation(Dict.class).dicCode();
|
||||
String text = field.getAnnotation(Dict.class).dicText();
|
||||
String table = field.getAnnotation(Dict.class).dictTable();
|
||||
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
// 自定义的字典表数据源
|
||||
String dataSource = field.getAnnotation(Dict.class).ds();
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
String fieldDictCode = code;
|
||||
if (!StringUtils.isEmpty(table)) {
|
||||
fieldDictCode = String.format("%s,%s,%s", table, text, code);
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
fieldDictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
}
|
||||
|
||||
String value = record.getString(field.getName());
|
||||
@ -274,9 +286,25 @@ public class DictAspect {
|
||||
String[] arr = dictCode.split(",");
|
||||
String table = arr[0], text = arr[1], code = arr[2];
|
||||
String values = String.join(",", needTranslDataTable);
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
// 自定义的数据源
|
||||
String dataSource = null;
|
||||
if (arr.length > 3) {
|
||||
dataSource = arr[3];
|
||||
}
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
|
||||
log.debug("translateDictFromTableByKeys.values:" + values);
|
||||
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values);
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
|
||||
//update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
||||
if(null == dataSource){
|
||||
dataSource = "";
|
||||
}
|
||||
//update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
||||
|
||||
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource);
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
log.debug("translateDictFromTableByKeys.result:" + texts);
|
||||
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||
list.addAll(texts);
|
||||
|
||||
@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
@ -59,8 +59,7 @@ public class PermissionDataAspect {
|
||||
requestPath = filterUrl(requestPath);
|
||||
//update-begin-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
||||
//先判断是否online报表请求
|
||||
// TODO 参数顺序调整有隐患
|
||||
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0){
|
||||
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0 || requestPath.indexOf(UrlMatchEnum.CGREPORT_ONLY_DATA.getMatchUrl())>=0){
|
||||
// 获取地址栏参数
|
||||
String urlParamString = request.getParameter(CommonConstant.ONL_REP_URL_PARAM_STR);
|
||||
if(oConvertUtils.isNotEmpty(urlParamString)){
|
||||
@ -68,7 +67,7 @@ public class PermissionDataAspect {
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:20211027 for:JTC-132【online报表权限】online报表带参数的菜单配置数据权限无效
|
||||
log.info("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
|
||||
log.debug("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
|
||||
String username = JwtUtil.getUserNameByToken(request);
|
||||
//查询数据权限信息
|
||||
//TODO 微服务情况下也得支持缓存机制
|
||||
|
||||
@ -14,6 +14,8 @@ public enum UrlMatchEnum {
|
||||
CGFORM_TREE_DATA("/online/cgform/api/getTreeData/", "/online/cgformList/"),
|
||||
/**求URL与菜单路由URL转换规则 /online/cgreport/api/getColumnsAndData/ */
|
||||
CGREPORT_DATA("/online/cgreport/api/getColumnsAndData/", "/online/cgreport/"),
|
||||
/** 求URL与菜单路由URL转换规则/online/cgreport/api/getData/ 【vue3报表数据请求地址】 */
|
||||
CGREPORT_ONLY_DATA("/online/cgreport/api/getData/", "/online/cgreport/"),
|
||||
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportXls/ */
|
||||
CGREPORT_EXCEL_DATA("/online/cgreport/api/exportXls/", "/online/cgreport/"),
|
||||
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportManySheetXls/ */
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
package org.jeecg.common.aspect.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import org.jeecg.common.constant.enums.LowAppAopEnum;
|
||||
|
||||
/**
|
||||
* 自动注入low_app_id
|
||||
*
|
||||
* @Author scott
|
||||
* @email jeecgos@163.com
|
||||
* @Date 2022年01月05日
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface AutoLowApp {
|
||||
|
||||
/**
|
||||
* 切面类型(add、delete、db_import等其他操作)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
LowAppAopEnum action();
|
||||
|
||||
/**
|
||||
* 业务类型(cgform等)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String bizType();
|
||||
|
||||
}
|
||||
@ -39,4 +39,16 @@ public @interface Dict {
|
||||
* @return 返回类型: String
|
||||
*/
|
||||
String dictTable() default "";
|
||||
|
||||
|
||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
/**
|
||||
* 方法描述: 数据字典表所在数据源名称
|
||||
* 作 者: chenrui
|
||||
* 日 期: 2023年12月20日-下午4:58
|
||||
*
|
||||
* @return 返回类型: String
|
||||
*/
|
||||
String ds() default "";
|
||||
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||
}
|
||||
|
||||
@ -36,6 +36,16 @@ public interface CommonConstant {
|
||||
*/
|
||||
int LOG_TYPE_2 = 2;
|
||||
|
||||
/**
|
||||
* 系统日志类型: 租户操作日志
|
||||
*/
|
||||
int LOG_TYPE_3 = 3;
|
||||
|
||||
/**
|
||||
* 系统日志类型: 异常
|
||||
*/
|
||||
int LOG_TYPE_4 = 4;
|
||||
|
||||
/**
|
||||
* 操作日志类型: 查询
|
||||
*/
|
||||
@ -69,6 +79,8 @@ public interface CommonConstant {
|
||||
|
||||
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
|
||||
Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
|
||||
/** {@code 404 Not Found} (HTTP/1.0 - RFC 1945) */
|
||||
Integer SC_INTERNAL_NOT_FOUND_404 = 404;
|
||||
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
|
||||
Integer SC_OK_200 = 200;
|
||||
|
||||
@ -78,7 +90,7 @@ public interface CommonConstant {
|
||||
/** 登录用户Shiro权限缓存KEY前缀 */
|
||||
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
|
||||
/** 登录用户Token令牌缓存KEY前缀 */
|
||||
String PREFIX_USER_TOKEN = "prefix_user_token_";
|
||||
String PREFIX_USER_TOKEN = "token::jeecg-client::";
|
||||
// /** Token缓存时间:3600秒即一小时 */
|
||||
// int TOKEN_EXPIRE_TIME = 3600;
|
||||
|
||||
@ -112,8 +124,8 @@ public interface CommonConstant {
|
||||
String HAS_CANCLE = "2";
|
||||
|
||||
/**阅读状态(0未读,1已读)*/
|
||||
String HAS_READ_FLAG = "1";
|
||||
String NO_READ_FLAG = "0";
|
||||
Integer HAS_READ_FLAG = 1;
|
||||
Integer NO_READ_FLAG = 0;
|
||||
|
||||
/**优先级(L低,M中,H高)*/
|
||||
String PRIORITY_L = "L";
|
||||
@ -152,13 +164,16 @@ public interface CommonConstant {
|
||||
Integer RULE_FLAG_1 = 1;
|
||||
|
||||
/**
|
||||
* 是否用户已被冻结 1正常(解冻) 2冻结
|
||||
* 是否用户已被冻结 1正常(解冻) 2冻结 3离职
|
||||
*/
|
||||
Integer USER_UNFREEZE = 1;
|
||||
Integer USER_FREEZE = 2;
|
||||
Integer USER_QUIT = 3;
|
||||
|
||||
/**字典翻译文本后缀*/
|
||||
String DICT_TEXT_SUFFIX = "_dictText";
|
||||
/**字典翻译颜色后缀*/
|
||||
String DICT_COLOR_SUFFIX = "_dictColor";
|
||||
|
||||
/**
|
||||
* 表单设计器主表类型
|
||||
@ -281,6 +296,10 @@ public interface CommonConstant {
|
||||
* 在线聊天 用户好友缓存前缀
|
||||
*/
|
||||
String IM_PREFIX_USER_FRIEND_CACHE = "sys:cache:im:im_prefix_user_friend_";
|
||||
/**
|
||||
* 缓存用户id与用户名关系
|
||||
*/
|
||||
String SYS_USER_ID_MAPPING_CACHE = "sys:cache:user:id_mapping";
|
||||
|
||||
/**
|
||||
* 考勤补卡业务状态 (1:同意 2:不同意)
|
||||
@ -312,8 +331,10 @@ public interface CommonConstant {
|
||||
String X_ACCESS_TOKEN = "X-Access-Token";
|
||||
String X_SIGN = "X-Sign";
|
||||
String X_TIMESTAMP = "X-TIMESTAMP";
|
||||
/** 租户 请求头*/
|
||||
String TENANT_ID = "tenant-id";
|
||||
/** 租户请求头 更名为:X-Tenant-Id */
|
||||
String TENANT_ID = "X-Tenant-Id";
|
||||
/** 简流接口请求头,用于排除不支持的控件字段 */
|
||||
String X_MiniFlowExclusionFieldMode = "X-Miniflowexclusionfieldmode";
|
||||
/**===============================================================================================*/
|
||||
|
||||
String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!";
|
||||
@ -370,6 +391,8 @@ public interface CommonConstant {
|
||||
/**前端vue3版本Header参数名*/
|
||||
String VERSION="X-Version";
|
||||
|
||||
String VERSION_V3 = "v3";
|
||||
|
||||
/**存储在线程变量里的动态表名*/
|
||||
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
|
||||
/**
|
||||
@ -387,6 +410,7 @@ public interface CommonConstant {
|
||||
/** 部门表唯一key,orgCode */
|
||||
String DEPART_KEY_ORG_CODE = "orgCode";
|
||||
|
||||
/**======【消息推送相关】==============================================================================*/
|
||||
/**
|
||||
* 发消息 会传递一些信息到map
|
||||
*/
|
||||
@ -397,6 +421,11 @@ public interface CommonConstant {
|
||||
*/
|
||||
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
|
||||
|
||||
/**
|
||||
* 发消息 消息业务类型
|
||||
*/
|
||||
String NOTICE_MSG_BUS_TYPE = "NOTICE_MSG_BUS_TYPE";
|
||||
|
||||
/**
|
||||
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
|
||||
*/
|
||||
@ -419,10 +448,173 @@ public interface CommonConstant {
|
||||
|
||||
/** 消息模板:markdown */
|
||||
String MSG_TEMPLATE_TYPE_MD = "5";
|
||||
/**========【消息推送相关】==========================================================================*/
|
||||
|
||||
/**
|
||||
* 短信验证码redis-key的前缀
|
||||
*/
|
||||
String PHONE_REDIS_KEY_PRE = "phone_msg";
|
||||
|
||||
/**
|
||||
* 是文件夹
|
||||
*/
|
||||
String IT_IS_FOLDER = "1";
|
||||
|
||||
/**
|
||||
* 文件拥有者
|
||||
*/
|
||||
String FILE_OWNER = "owner";
|
||||
|
||||
/**
|
||||
* 文件管理员
|
||||
*/
|
||||
String FILE_ADMIN = "admin";
|
||||
|
||||
/**
|
||||
* 只允许编辑
|
||||
*/
|
||||
String FILE_EDITABLE = "editable";
|
||||
|
||||
/**
|
||||
* 登录失败,用于记录失败次数的key
|
||||
*/
|
||||
String LOGIN_FAIL = "LOGIN_FAIL_";
|
||||
|
||||
/**
|
||||
* 入职事件
|
||||
*/
|
||||
Integer BPM_USER_EVENT_ADD = 1;
|
||||
|
||||
/**
|
||||
* 离职事件
|
||||
*/
|
||||
Integer BPM_USER_EVENT_LEVEL = 2;
|
||||
|
||||
/**
|
||||
* 用户租户状态(正常/已通过审核的)
|
||||
*/
|
||||
String USER_TENANT_NORMAL = "1";
|
||||
|
||||
/**
|
||||
* 用户租户状态(离职)
|
||||
*/
|
||||
String USER_TENANT_QUIT = "2";
|
||||
|
||||
/**
|
||||
* 用户租户状态(审核中)
|
||||
*/
|
||||
String USER_TENANT_UNDER_REVIEW = "3";
|
||||
|
||||
/**
|
||||
* 用户租户状态(拒绝)
|
||||
*/
|
||||
String USER_TENANT_REFUSE = "4";
|
||||
|
||||
/**
|
||||
* 用户租户状态(邀请)
|
||||
*/
|
||||
String USER_TENANT_INVITE = "5";
|
||||
|
||||
/**
|
||||
* 不是叶子节点
|
||||
*/
|
||||
Integer NOT_LEAF = 0;
|
||||
|
||||
/**
|
||||
* 是叶子节点
|
||||
*/
|
||||
Integer IS_LEAF = 1;
|
||||
|
||||
/**
|
||||
* 钉钉
|
||||
*/
|
||||
String DINGTALK = "DINGTALK";
|
||||
|
||||
/**
|
||||
* 企业微信
|
||||
*/
|
||||
String WECHAT_ENTERPRISE = "WECHAT_ENTERPRISE";
|
||||
|
||||
/**
|
||||
* 系统默认租户id 0
|
||||
*/
|
||||
Integer TENANT_ID_DEFAULT_VALUE = 0;
|
||||
|
||||
/**
|
||||
* 【low-app用】 应用级别的复制
|
||||
*/
|
||||
String COPY_LEVEL_APP = "app";
|
||||
|
||||
/**
|
||||
* 【low-app用】 菜单级别的复制
|
||||
*/
|
||||
String COPY_LEVEL_MENU = "menu";
|
||||
|
||||
|
||||
/**
|
||||
* 【low-app用】 应用备份
|
||||
*/
|
||||
String COPY_LEVEL_BAK = "backup";
|
||||
|
||||
/**
|
||||
* 【low-app用】 从备份还原
|
||||
*/
|
||||
String COPY_LEVEL_COVER = "cover";
|
||||
|
||||
/** 【QQYUN-6034】关联字段变更历史值,缓存半个小时 */
|
||||
String CACHE_REL_FIELD_OLD_VAL = "sys:cache:desform:relFieldOldVal:";
|
||||
|
||||
/**
|
||||
* 排序类型:升序
|
||||
*/
|
||||
String ORDER_TYPE_ASC = "ASC";
|
||||
/**
|
||||
* 排序类型:降序
|
||||
*/
|
||||
String ORDER_TYPE_DESC = "DESC";
|
||||
|
||||
|
||||
//update-begin---author:scott ---date:2023-09-10 for:积木报表常量----
|
||||
/**
|
||||
* 报表允许设计开发的角色
|
||||
*/
|
||||
public static String[] allowDevRoles = new String[]{"lowdeveloper", "admin"};
|
||||
/**
|
||||
* 【对应积木报表的常量】
|
||||
* 数据隔离模式: 按照创建人隔离
|
||||
*/
|
||||
public static final String SAAS_MODE_CREATED = "created";
|
||||
/**
|
||||
* 【对应积木报表的常量】
|
||||
* 数据隔离模式: 按照租户隔离
|
||||
*/
|
||||
public static final String SAAS_MODE_TENANT = "tenant";
|
||||
//update-end---author:scott ---date::2023-09-10 for:积木报表常量----
|
||||
|
||||
//update-begin---author:wangshuai---date:2024-04-07---for:修改手机号常量---
|
||||
/**
|
||||
* 修改手机号短信验证码redis-key的前缀
|
||||
*/
|
||||
String CHANGE_PHONE_REDIS_KEY_PRE = "sys:cache:phone:change_phone_msg:";
|
||||
|
||||
/**
|
||||
* 缓存用户最后一次收到消息通知的时间 KEY
|
||||
*/
|
||||
String CACHE_KEY_USER_LAST_ANNOUNT_TIME_1HOUR = "sys:cache:userinfo:user_last_annount_time::%s";
|
||||
|
||||
/**
|
||||
* 验证原手机号
|
||||
*/
|
||||
String VERIFY_ORIGINAL_PHONE = "verifyOriginalPhone";
|
||||
|
||||
/**
|
||||
* 修改手机号
|
||||
*/
|
||||
String UPDATE_PHONE = "updatePhone";
|
||||
//update-end---author:wangshuai---date:2024-04-07---for:修改手机号常量---
|
||||
|
||||
/**
|
||||
* 修改手机号验证码请求次数超出
|
||||
*/
|
||||
Integer PHONE_SMS_FAIL_CODE = 40002;
|
||||
}
|
||||
|
||||
@ -28,12 +28,21 @@ public interface CommonSendStatus {
|
||||
public static final String APP_SESSION_SUFFIX = "_app";
|
||||
|
||||
|
||||
/**-----【流程相关通知模板code】------------------------------------------------------------*/
|
||||
/**流程催办——系统通知消息模板*/
|
||||
public static final String TZMB_BPM_CUIBAN = "bpm_cuiban";
|
||||
/**流程抄送——系统通知消息模板*/
|
||||
public static final String TZMB_BPM_CC = "bpm_cc";
|
||||
/**流程催办——邮件通知消息模板*/
|
||||
public static final String TZMB_BPM_CUIBAN_EMAIL = "bpm_cuiban_email";
|
||||
/**标准模板—系统消息通知*/
|
||||
public static final String TZMB_SYS_TS_NOTE = "sys_ts_note";
|
||||
/**流程超时提醒——系统通知消息模板*/
|
||||
public static final String TZMB_BPM_CHAOSHI_TIP = "bpm_chaoshi_tip";
|
||||
/**-----【流程相关通知模板code】-----------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 系统通知拓展参数(比如:用于流程抄送和催办通知,这里额外传递流程跳转页面所需要的路由参数)
|
||||
*/
|
||||
public static final String MSG_ABSTRACT_JSON = "msg_abstract";
|
||||
}
|
||||
|
||||
@ -17,6 +17,9 @@ public interface DataBaseConstant {
|
||||
|
||||
/**postgreSQL达梦数据库*/
|
||||
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
|
||||
|
||||
/**人大金仓数据库*/
|
||||
public static final String DB_TYPE_KINGBASEES = "KINGBASEES";
|
||||
|
||||
/**sqlserver数据库*/
|
||||
public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
|
||||
@ -55,6 +58,22 @@ public interface DataBaseConstant {
|
||||
* 数据-所属机构编码
|
||||
*/
|
||||
public static final String SYS_MULTI_ORG_CODE_TABLE = "sys_multi_org_code";
|
||||
/**
|
||||
* 数据-所属机构ID
|
||||
*/
|
||||
public static final String SYS_ORG_ID = "sysOrgId";
|
||||
/**
|
||||
* 数据-所属机构ID
|
||||
*/
|
||||
public static final String SYS_ORG_ID_TABLE = "sys_org_id";
|
||||
/**
|
||||
* 数据-所属角色code(多个逗号分割)
|
||||
*/
|
||||
public static final String SYS_ROLE_CODE = "sysRoleCode";
|
||||
/**
|
||||
* 数据-所属角色code(多个逗号分割)
|
||||
*/
|
||||
public static final String SYS_ROLE_CODE_TABLE = "sys_role_code";
|
||||
/**
|
||||
* 数据-系统用户编码(对应登录用户账号)
|
||||
*/
|
||||
@ -63,7 +82,14 @@ public interface DataBaseConstant {
|
||||
* 数据-系统用户编码(对应登录用户账号)
|
||||
*/
|
||||
public static final String SYS_USER_CODE_TABLE = "sys_user_code";
|
||||
|
||||
/**
|
||||
* 登录用户ID
|
||||
*/
|
||||
public static final String SYS_USER_ID = "sysUserId";
|
||||
/**
|
||||
* 登录用户ID
|
||||
*/
|
||||
public static final String SYS_USER_ID_TABLE = "sys_user_id";
|
||||
/**
|
||||
* 登录用户真实姓名
|
||||
*/
|
||||
@ -139,16 +165,6 @@ public interface DataBaseConstant {
|
||||
public static final String BPM_STATUS_TABLE = "bpm_status";
|
||||
//*********系统建表标准字段****************************************
|
||||
|
||||
|
||||
/**
|
||||
* 租户ID 实体字段名
|
||||
*/
|
||||
String TENANT_ID = "tenantId";
|
||||
/**
|
||||
* 租户ID 数据库字段名
|
||||
*/
|
||||
String TENANT_ID_TABLE = "tenant_id";
|
||||
|
||||
/**
|
||||
* sql语句 where
|
||||
*/
|
||||
|
||||
@ -33,6 +33,23 @@ public interface ServiceNameConstants {
|
||||
* 微服务名: demo模块
|
||||
*/
|
||||
String SERVICE_DEMO = "jeecg-demo";
|
||||
/**
|
||||
* 微服务名:joa模块
|
||||
*/
|
||||
String SERVICE_JOA = "jeecg-joa";
|
||||
|
||||
// /**
|
||||
// * 微服务名:online在线模块
|
||||
// */
|
||||
// String SERVICE_ONLINE = "jeecg-online";
|
||||
// /**
|
||||
// * 微服务名:OA模块
|
||||
// */
|
||||
// String SERVICE_EOA = "jeecg-eoa";
|
||||
// /**
|
||||
// * 微服务名:表单设计模块
|
||||
// */
|
||||
// String SERVICE_FORM = "jeecg-desform";
|
||||
|
||||
/**
|
||||
* gateway通过header传递根路径 basePath
|
||||
|
||||
@ -116,4 +116,8 @@ public class SymbolConstant {
|
||||
*/
|
||||
public static final String SQUARE_BRACKETS_RIGHT = "]";
|
||||
|
||||
/**
|
||||
* 拼接字符串符号 分号 ;
|
||||
*/
|
||||
public static final String SEMICOLON = ";";
|
||||
}
|
||||
@ -6,17 +6,45 @@ package org.jeecg.common.constant;
|
||||
* @date: 2022年08月29日 15:29
|
||||
*/
|
||||
public interface TenantConstant {
|
||||
|
||||
/*------【低代码应用参数】----------------------------------------------*/
|
||||
/**
|
||||
* 应用ID——表字段
|
||||
* header的lowAppId标识
|
||||
*/
|
||||
String DB_FIELD_LOW_APP_ID = "low_app_id";
|
||||
String X_LOW_APP_ID = "X-Low-App-ID";
|
||||
/**
|
||||
* 应用ID——实体字段
|
||||
*/
|
||||
String FIELD_LOW_APP_ID = "lowAppId";
|
||||
/**
|
||||
* 租户ID
|
||||
* 应用ID——表字段
|
||||
*/
|
||||
String DB_FIELD_LOW_APP_ID = "low_app_id";
|
||||
/*------【低代码应用参数】---------------------------------------------*/
|
||||
|
||||
/*--------【租户参数】-----------------------------------------------*/
|
||||
/**
|
||||
* 租户ID(实体字段名 和 url参数名)
|
||||
*/
|
||||
String TENANT_ID = "tenantId";
|
||||
/**
|
||||
* 租户ID 数据库字段名
|
||||
*/
|
||||
String TENANT_ID_TABLE = "tenant_id";
|
||||
/*-------【租户参数】-----------------------------------------------*/
|
||||
|
||||
/**
|
||||
* 超级管理员
|
||||
*/
|
||||
String SUPER_ADMIN = "superAdmin";
|
||||
|
||||
/**
|
||||
* 组织账户管理员
|
||||
*/
|
||||
String ACCOUNT_ADMIN = "accountAdmin";
|
||||
|
||||
/**
|
||||
* 组织应用管理员
|
||||
*/
|
||||
String APP_ADMIN = "appAdmin";
|
||||
|
||||
}
|
||||
|
||||
@ -14,32 +14,33 @@ public enum CgformEnum {
|
||||
/**
|
||||
* 单表
|
||||
*/
|
||||
ONE(1, "one", "/jeecg/code-template-online", "default.one", "经典风格"),
|
||||
/**
|
||||
* 多表
|
||||
*/
|
||||
MANY(2, "many", "/jeecg/code-template-online", "default.onetomany", "经典风格"),
|
||||
/**
|
||||
* 多表(jvxe风格)
|
||||
* */
|
||||
JVXE_TABLE(2, "jvxe", "/jeecg/code-template-online", "jvxe.onetomany", "JVXE风格"),
|
||||
ONE(1, "one", "/jeecg/code-template-online", "default.one", "经典风格", new String[]{"vue3","vue","vue3Native"}),
|
||||
|
||||
/**
|
||||
* 多表
|
||||
*/
|
||||
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格"),
|
||||
MANY(2, "many", "/jeecg/code-template-online", "default.onetomany", "经典风格" ,new String[]{"vue"}),
|
||||
/**
|
||||
* 多表(jvxe风格)
|
||||
* */
|
||||
JVXE_TABLE(2, "jvxe", "/jeecg/code-template-online", "jvxe.onetomany", "默认风格" ,new String[]{"vue3","vue","vue3Native"}),
|
||||
|
||||
/**
|
||||
* 多表 (erp风格)
|
||||
*/
|
||||
ERP(2, "erp", "/jeecg/code-template-online", "erp.onetomany", "ERP风格" ,new String[]{"vue3","vue","vue3Native"}),
|
||||
/**
|
||||
* 多表(内嵌子表风格)
|
||||
*/
|
||||
INNER_TABLE(2, "innerTable", "/jeecg/code-template-online", "inner-table.onetomany", "内嵌子表风格"),
|
||||
INNER_TABLE(2, "innerTable", "/jeecg/code-template-online", "inner-table.onetomany", "内嵌子表风格" ,new String[]{"vue3","vue"}),
|
||||
/**
|
||||
* 多表(tab风格)
|
||||
* */
|
||||
TAB(2, "tab", "/jeecg/code-template-online", "tab.onetomany", "Tab风格"),
|
||||
TAB(2, "tab", "/jeecg/code-template-online", "tab.onetomany", "Tab风格" ,new String[]{"vue3","vue"}),
|
||||
/**
|
||||
* 树形列表
|
||||
*/
|
||||
TREE(3, "tree", "/jeecg/code-template-online", "default.tree", "树形列表");
|
||||
TREE(3, "tree", "/jeecg/code-template-online", "default.tree", "树形列表" ,new String[]{"vue3","vue","vue3Native"});
|
||||
|
||||
/**
|
||||
* 类型 1/单表 2/一对多 3/树
|
||||
@ -61,6 +62,10 @@ public enum CgformEnum {
|
||||
* 模板风格名称
|
||||
*/
|
||||
String note;
|
||||
/**
|
||||
* 支持代码风格 vue3:vue3包装代码 vue3Native:vue3原生代码 vue:vue2代码
|
||||
*/
|
||||
String[] vueStyle;
|
||||
|
||||
/**
|
||||
* 构造器
|
||||
@ -70,13 +75,15 @@ public enum CgformEnum {
|
||||
* @param templatePath 模板路径
|
||||
* @param stylePath 模板子路径
|
||||
* @param note
|
||||
* @param vueStyle 支持代码风格
|
||||
*/
|
||||
CgformEnum(int type, String code, String templatePath, String stylePath, String note) {
|
||||
CgformEnum(int type, String code, String templatePath, String stylePath, String note, String[] vueStyle) {
|
||||
this.type = type;
|
||||
this.code = code;
|
||||
this.templatePath = templatePath;
|
||||
this.stylePath = stylePath;
|
||||
this.note = note;
|
||||
this.vueStyle = vueStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,6 +121,14 @@ public enum CgformEnum {
|
||||
this.stylePath = stylePath;
|
||||
}
|
||||
|
||||
public String[] getVueStyle() {
|
||||
return vueStyle;
|
||||
}
|
||||
|
||||
public void setVueStyle(String[] vueStyle) {
|
||||
this.vueStyle = vueStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code找枚举
|
||||
*
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
/**
|
||||
* 客户终端类型
|
||||
*/
|
||||
public enum ClientTerminalTypeEnum {
|
||||
|
||||
PC("pc", "电脑终端"),
|
||||
H5("h5", "移动网页端"),
|
||||
APP("app", "手机app端");
|
||||
|
||||
private String key;
|
||||
private String text;
|
||||
|
||||
ClientTerminalTypeEnum(String value, String text) {
|
||||
this.key = value;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
/**
|
||||
* 日期预设范围枚举
|
||||
*/
|
||||
public enum DateRangeEnum {
|
||||
// 今天
|
||||
TODAY,
|
||||
// 昨天
|
||||
YESTERDAY,
|
||||
// 明天
|
||||
TOMORROW,
|
||||
// 本周
|
||||
THIS_WEEK,
|
||||
// 上周
|
||||
LAST_WEEK,
|
||||
// 下周
|
||||
NEXT_WEEK,
|
||||
// 过去七天
|
||||
LAST_7_DAYS,
|
||||
// 本月
|
||||
THIS_MONTH,
|
||||
// 上月
|
||||
LAST_MONTH,
|
||||
// 下月
|
||||
NEXT_MONTH,
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package org.jeecg.common.util;
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@ -9,15 +9,21 @@ import org.apache.commons.lang3.StringUtils;
|
||||
public enum DySmsEnum {
|
||||
|
||||
/**登录短信模板编码*/
|
||||
LOGIN_TEMPLATE_CODE("SMS_175435174","JEECG","code"),
|
||||
LOGIN_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
|
||||
/**忘记密码短信模板编码*/
|
||||
FORGET_PASSWORD_TEMPLATE_CODE("SMS_175435174","JEECG","code"),
|
||||
/**注册账号短信模板编码*/
|
||||
REGISTER_TEMPLATE_CODE("SMS_175430166","JEECG","code"),
|
||||
FORGET_PASSWORD_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
|
||||
/**修改密码短信模板编码*/
|
||||
CHANGE_PASSWORD_TEMPLATE_CODE("SMS_465391221","敲敲云","code"),
|
||||
/**注册账号短信模板编码*/
|
||||
REGISTER_TEMPLATE_CODE("SMS_175430166","敲敲云","code"),
|
||||
/**会议通知*/
|
||||
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","H5活动之家","username,title,minute,time"),
|
||||
MEET_NOTICE_TEMPLATE_CODE("SMS_201480469","JEECG","username,title,minute,time"),
|
||||
/**我的计划通知*/
|
||||
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","H5活动之家","username,title,time");
|
||||
PLAN_NOTICE_TEMPLATE_CODE("SMS_201470515","JEECG","username,title,time"),
|
||||
/**支付成功短信通知*/
|
||||
PAY_SUCCESS_NOTICE_CODE("SMS_461735163","敲敲云","realname,money,endTime"),
|
||||
/**会员到期通知提醒*/
|
||||
VIP_EXPIRE_NOTICE_CODE("SMS_461885023","敲敲云","realname,endTime");
|
||||
|
||||
/**
|
||||
* 短信模板编码
|
||||
@ -0,0 +1,66 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
|
||||
/**
|
||||
* 邮件html模板配置地址美剧
|
||||
*
|
||||
* @author: liusq
|
||||
* @Date: 2023-10-13
|
||||
*/
|
||||
public enum EmailTemplateEnum {
|
||||
/**
|
||||
* 流程催办
|
||||
*/
|
||||
BPM_CUIBAN_EMAIL("bpm_cuiban_email", "/templates/email/bpm_cuiban_email.ftl"),
|
||||
/**
|
||||
* 流程新任务
|
||||
*/
|
||||
BPM_NEW_TASK_EMAIL("bpm_new_task_email", "/templates/email/bpm_new_task_email.ftl"),
|
||||
/**
|
||||
* 表单新增记录
|
||||
*/
|
||||
DESFORM_NEW_DATA_EMAIL("desform_new_data_email", "/templates/email/desform_new_data_email.ftl");
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 模板地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
EmailTemplateEnum(String name, String url) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public static EmailTemplateEnum getByName(String name) {
|
||||
if (oConvertUtils.isEmpty(name)) {
|
||||
return null;
|
||||
}
|
||||
for (EmailTemplateEnum val : values()) {
|
||||
if (val.getName().equals(name)) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ import org.jeecg.common.util.oConvertUtils;
|
||||
* 文件类型
|
||||
*/
|
||||
public enum FileTypeEnum {
|
||||
// 文档类型(folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频)
|
||||
// 文档类型(folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频 voice:语音)
|
||||
// FOLDER
|
||||
xls(".xls","excel","excel"),
|
||||
xlsx(".xlsx","excel","excel"),
|
||||
@ -26,7 +26,8 @@ public enum FileTypeEnum {
|
||||
flv(".flv","video","视频"),
|
||||
mp4(".mp4","video","视频"),
|
||||
zip(".zip","zip","压缩包"),
|
||||
pdf(".pdf","pdf","pdf");
|
||||
pdf(".pdf","pdf","pdf"),
|
||||
mp3(".mp3","mp3","语音");
|
||||
|
||||
private String type;
|
||||
private String value;
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
/**
|
||||
* LowApp 切面注解枚举
|
||||
* @date 2022-1-5
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public enum LowAppAopEnum {
|
||||
|
||||
/**
|
||||
* 新增方法
|
||||
*/
|
||||
ADD,
|
||||
/**
|
||||
* 删除方法(包含单个和批量删除)
|
||||
*/
|
||||
DELETE,
|
||||
/** 复制表单操作 */
|
||||
COPY,
|
||||
|
||||
/**
|
||||
* Online表单专用:数据库表转Online表单
|
||||
*/
|
||||
CGFORM_DB_IMPORT
|
||||
}
|
||||
@ -13,12 +13,16 @@ import java.util.List;
|
||||
public enum RoleIndexConfigEnum {
|
||||
|
||||
/**首页自定义 admin*/
|
||||
ADMIN("admin", "dashboard/Analysis"),
|
||||
// ADMIN("admin", "dashboard/Analysis"),
|
||||
//TEST("test", "dashboard/IndexChart"),
|
||||
/**首页自定义 hr*/
|
||||
HR("hr", "dashboard/IndexBdc");
|
||||
// HR("hr", "dashboard/IndexBdc");
|
||||
|
||||
//DM("dm", "dashboard/IndexTask"),
|
||||
|
||||
// 注:此值仅为防止报错,无任何实际意义
|
||||
ROLE_INDEX_CONFIG_ENUM("RoleIndexConfigEnumDefault", "dashboard/Analysis");
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
package org.jeecg.common.util;
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
|
||||
/**
|
||||
* 系统公告自定义跳转方式
|
||||
@ -12,7 +14,16 @@ public enum SysAnnmentTypeEnum {
|
||||
/**
|
||||
* 流程跳转到我的任务
|
||||
*/
|
||||
BPM("bpm", "url", "/bpm/task/MyTaskList");
|
||||
BPM("bpm", "url", "/bpm/task/MyTaskList"),
|
||||
|
||||
/**
|
||||
* 流程抄送任务
|
||||
*/
|
||||
BPM_VIEW("bpm_cc", "url", "/bpm/task/MyTaskList"),
|
||||
/**
|
||||
* 邀请用户跳转到个人设置
|
||||
*/
|
||||
TENANT_INVITE("tenant_invite", "url", "/system/usersetting");
|
||||
|
||||
/**
|
||||
* 业务类型(email:邮件 bpm:流程)
|
||||
@ -1,4 +1,4 @@
|
||||
package org.jeecg.modules.message.enums;
|
||||
package org.jeecg.common.constant.enums;
|
||||
|
||||
import org.jeecg.common.system.annotation.EnumDict;
|
||||
import org.jeecg.common.system.vo.DictModel;
|
||||
@ -18,6 +18,16 @@ public enum Vue3MessageHrefEnum {
|
||||
* 流程催办
|
||||
*/
|
||||
BPM("bpm", "/task/myHandleTaskInfo"),
|
||||
|
||||
/**
|
||||
* 系统消息通知
|
||||
*/
|
||||
BPM_SYSTEM_MSG("bpm_msg_node", ""),
|
||||
|
||||
/**
|
||||
* 流程抄送任务
|
||||
*/
|
||||
BPM_VIEW("bpm_cc", "/task/myHandleTaskInfo"),
|
||||
|
||||
/**
|
||||
* 节点通知
|
||||
@ -0,0 +1,87 @@
|
||||
package org.jeecg.common.desensitization;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.desensitization.annotation.Sensitive;
|
||||
import org.jeecg.common.desensitization.enums.SensitiveEnum;
|
||||
import org.jeecg.common.desensitization.util.SensitiveInfoUtil;
|
||||
import org.jeecg.common.util.encryption.AesEncryptUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author eightmonth@qq.com
|
||||
* @date 2024/6/19 10:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
|
||||
|
||||
private SensitiveEnum type;
|
||||
|
||||
@Override
|
||||
public void serialize(String data, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
switch (type){
|
||||
case ENCODE:
|
||||
try {
|
||||
jsonGenerator.writeString(AesEncryptUtil.encrypt(data));
|
||||
} catch (Exception exception) {
|
||||
log.error("数据加密错误", exception.getMessage());
|
||||
jsonGenerator.writeString(data);
|
||||
}
|
||||
break;
|
||||
case CHINESE_NAME:
|
||||
jsonGenerator.writeString(SensitiveInfoUtil.chineseName(data));
|
||||
break;
|
||||
case ID_CARD:
|
||||
jsonGenerator.writeString(SensitiveInfoUtil.idCardNum(data));
|
||||
break;
|
||||
case FIXED_PHONE:
|
||||
jsonGenerator.writeString(SensitiveInfoUtil.fixedPhone(data));
|
||||
break;
|
||||
case MOBILE_PHONE:
|
||||
jsonGenerator.writeString(SensitiveInfoUtil.mobilePhone(data));
|
||||
break;
|
||||
case ADDRESS:
|
||||
jsonGenerator.writeString(SensitiveInfoUtil.address(data, 3));
|
||||
break;
|
||||
case EMAIL:
|
||||
jsonGenerator.writeString(SensitiveInfoUtil.email(data));
|
||||
break;
|
||||
case BANK_CARD:
|
||||
jsonGenerator.writeString(SensitiveInfoUtil.bankCard(data));
|
||||
break;
|
||||
case CNAPS_CODE:
|
||||
jsonGenerator.writeString(SensitiveInfoUtil.cnapsCode(data));
|
||||
break;
|
||||
default:
|
||||
jsonGenerator.writeString(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
|
||||
if (beanProperty != null) {
|
||||
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
|
||||
Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
|
||||
if (sensitive == null) {
|
||||
sensitive = beanProperty.getContextAnnotation(Sensitive.class);
|
||||
}
|
||||
if (sensitive != null) {
|
||||
return new SensitiveSerialize(sensitive.type());
|
||||
}
|
||||
}
|
||||
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
|
||||
}
|
||||
return serializerProvider.findNullValueSerializer(null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package org.jeecg.common.desensitization.annotation;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.jeecg.common.desensitization.SensitiveSerialize;
|
||||
import org.jeecg.common.desensitization.enums.SensitiveEnum;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 在字段上定义 标识字段存储的信息是敏感的
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@JacksonAnnotationsInside
|
||||
@JsonSerialize(using = SensitiveSerialize.class)
|
||||
public @interface Sensitive {
|
||||
|
||||
/**
|
||||
* 不同类型处理不同
|
||||
* @return
|
||||
*/
|
||||
SensitiveEnum type() default SensitiveEnum.ENCODE;
|
||||
}
|
||||
@ -63,11 +63,12 @@ public class SensitiveInfoUtil {
|
||||
* @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;
|
||||
}
|
||||
long startTime=System.currentTimeMillis();
|
||||
log.debug(" obj --> "+ obj.toString());
|
||||
|
||||
// 判断是不是一个对象
|
||||
Field[] fields = obj.getClass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
@ -197,7 +198,7 @@ public class SensitiveInfoUtil {
|
||||
* @param fullName 全名
|
||||
* @return <例子:李**>
|
||||
*/
|
||||
private static String chineseName(String fullName) {
|
||||
public static String chineseName(String fullName) {
|
||||
if (oConvertUtils.isEmpty(fullName)) {
|
||||
return "";
|
||||
}
|
||||
@ -210,7 +211,7 @@ public class SensitiveInfoUtil {
|
||||
* @param firstName 名
|
||||
* @return <例子:李**>
|
||||
*/
|
||||
private static String chineseName(String familyName, String firstName) {
|
||||
public static String chineseName(String familyName, String firstName) {
|
||||
if (oConvertUtils.isEmpty(familyName) || oConvertUtils.isEmpty(firstName)) {
|
||||
return "";
|
||||
}
|
||||
@ -222,7 +223,7 @@ public class SensitiveInfoUtil {
|
||||
* @param id 身份证号
|
||||
* @return <例子:*************5762>
|
||||
*/
|
||||
private static String idCardNum(String id) {
|
||||
public static String idCardNum(String id) {
|
||||
if (oConvertUtils.isEmpty(id)) {
|
||||
return "";
|
||||
}
|
||||
@ -235,7 +236,7 @@ public class SensitiveInfoUtil {
|
||||
* @param num 固定电话
|
||||
* @return <例子:****1234>
|
||||
*/
|
||||
private static String fixedPhone(String num) {
|
||||
public static String fixedPhone(String num) {
|
||||
if (oConvertUtils.isEmpty(num)) {
|
||||
return "";
|
||||
}
|
||||
@ -247,7 +248,7 @@ public class SensitiveInfoUtil {
|
||||
* @param num 手机号码
|
||||
* @return <例子:138******1234>
|
||||
*/
|
||||
private static String mobilePhone(String num) {
|
||||
public static String mobilePhone(String num) {
|
||||
if (oConvertUtils.isEmpty(num)) {
|
||||
return "";
|
||||
}
|
||||
@ -264,7 +265,7 @@ public class SensitiveInfoUtil {
|
||||
* @param sensitiveSize 敏感信息长度
|
||||
* @return <例子:北京市海淀区****>
|
||||
*/
|
||||
private static String address(String address, int sensitiveSize) {
|
||||
public static String address(String address, int sensitiveSize) {
|
||||
if (oConvertUtils.isEmpty(address)) {
|
||||
return "";
|
||||
}
|
||||
@ -280,7 +281,7 @@ public class SensitiveInfoUtil {
|
||||
* @param email 电子邮箱
|
||||
* @return <例子:g**@163.com>
|
||||
*/
|
||||
private static String email(String email) {
|
||||
public static String email(String email) {
|
||||
if (oConvertUtils.isEmpty(email)) {
|
||||
return "";
|
||||
}
|
||||
@ -299,7 +300,7 @@ public class SensitiveInfoUtil {
|
||||
* @param cardNum 银行卡号
|
||||
* @return <例子:6222600**********1234>
|
||||
*/
|
||||
private static String bankCard(String cardNum) {
|
||||
public static String bankCard(String cardNum) {
|
||||
if (oConvertUtils.isEmpty(cardNum)) {
|
||||
return "";
|
||||
}
|
||||
@ -311,7 +312,7 @@ public class SensitiveInfoUtil {
|
||||
* @param code 公司开户银行联号
|
||||
* @return <例子:12********>
|
||||
*/
|
||||
private static String cnapsCode(String code) {
|
||||
public static String cnapsCode(String code) {
|
||||
if (oConvertUtils.isEmpty(code)) {
|
||||
return "";
|
||||
}
|
||||
@ -325,7 +326,7 @@ public class SensitiveInfoUtil {
|
||||
* @param reservedLength 保留长度
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
private static String formatRight(String str, int reservedLength){
|
||||
public 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;
|
||||
@ -337,7 +338,7 @@ public class SensitiveInfoUtil {
|
||||
* @param reservedLength 保留长度
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
private static String formatLeft(String str, int reservedLength){
|
||||
public 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, "*"));
|
||||
@ -351,7 +352,7 @@ public class SensitiveInfoUtil {
|
||||
* @param endLen 结尾保留长度
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
private static String formatBetween(String str, int beginLen, int endLen){
|
||||
public 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);
|
||||
|
||||
@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.util.RestUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@ -22,6 +23,7 @@ import java.util.*;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "jeecg.elasticsearch", name = "cluster-nodes")
|
||||
public class JeecgElasticsearchTemplate {
|
||||
/** es服务地址 */
|
||||
private String baseUrl;
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package org.jeecg.common.exception;
|
||||
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
||||
/**
|
||||
* @Description: 业务提醒异常(用于操作业务提醒)
|
||||
* @date: 2024-04-26
|
||||
* @author: scott
|
||||
*/
|
||||
public class JeecgBootBizTipException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 返回给前端的错误code
|
||||
*/
|
||||
private int errCode = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
|
||||
|
||||
public JeecgBootBizTipException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
public JeecgBootBizTipException(String message, int errCode){
|
||||
super(message);
|
||||
this.errCode = errCode;
|
||||
}
|
||||
|
||||
public int getErrCode() {
|
||||
return errCode;
|
||||
}
|
||||
|
||||
public JeecgBootBizTipException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public JeecgBootBizTipException(String message, Throwable cause)
|
||||
{
|
||||
super(message,cause);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
package org.jeecg.common.exception;
|
||||
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
||||
/**
|
||||
* @Description: jeecg-boot自定义异常
|
||||
* @author: jeecg-boot
|
||||
@ -7,10 +9,24 @@ package org.jeecg.common.exception;
|
||||
public class JeecgBootException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 返回给前端的错误code
|
||||
*/
|
||||
private int errCode = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
|
||||
|
||||
public JeecgBootException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
public JeecgBootException(String message, int errCode){
|
||||
super(message);
|
||||
this.errCode = errCode;
|
||||
}
|
||||
|
||||
public int getErrCode() {
|
||||
return errCode;
|
||||
}
|
||||
|
||||
public JeecgBootException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
|
||||
@ -1,14 +1,30 @@
|
||||
package org.jeecg.common.exception;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.dto.LogDTO;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.enums.ClientTerminalTypeEnum;
|
||||
import org.jeecg.common.enums.SentinelErrorInfoEnum;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.BrowserUtils;
|
||||
import org.jeecg.common.util.IpUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.data.redis.connection.PoolException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
@ -16,7 +32,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 异常处理器
|
||||
@ -27,6 +43,27 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class JeecgBootExceptionHandler {
|
||||
|
||||
@Resource
|
||||
BaseCommonService baseCommonService;
|
||||
|
||||
/**
|
||||
* 验证码错误异常
|
||||
*/
|
||||
|
||||
@ExceptionHandler(JeecgCaptchaException.class)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public Result<?> handleJeecgCaptchaException(JeecgCaptchaException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(AuthenticationException.class)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public Result<?> handleJeecgCaptchaException(AuthenticationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error(401, e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义异常
|
||||
@ -34,7 +71,17 @@ public class JeecgBootExceptionHandler {
|
||||
@ExceptionHandler(JeecgBootException.class)
|
||||
public Result<?> handleJeecgBootException(JeecgBootException e){
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error(e.getMessage());
|
||||
addSysLog(e);
|
||||
return Result.error(e.getErrCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义异常
|
||||
*/
|
||||
@ExceptionHandler(JeecgBootBizTipException.class)
|
||||
public Result<?> handleJeecgBootBizTipException(JeecgBootBizTipException e){
|
||||
log.error(e.getMessage());
|
||||
return Result.error(e.getErrCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,6 +90,7 @@ public class JeecgBootExceptionHandler {
|
||||
@ExceptionHandler(JeecgCloudException.class)
|
||||
public Result<?> handleJeecgCloudException(JeecgCloudException e){
|
||||
log.error(e.getMessage(), e);
|
||||
addSysLog(e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
|
||||
@ -53,25 +101,28 @@ public class JeecgBootExceptionHandler {
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public Result<?> handleJeecgBoot401Exception(JeecgBoot401Exception e){
|
||||
log.error(e.getMessage(), e);
|
||||
addSysLog(e);
|
||||
return new Result(401,e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
public Result<?> handlerNoFoundException(Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
addSysLog(e);
|
||||
return Result.error(404, "路径不存在,请检查路径是否正确");
|
||||
}
|
||||
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public Result<?> handleDuplicateKeyException(DuplicateKeyException e){
|
||||
log.error(e.getMessage(), e);
|
||||
addSysLog(e);
|
||||
return Result.error("数据库中已存在该记录");
|
||||
}
|
||||
|
||||
@ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
|
||||
public Result<?> handleAuthorizationException(AuthorizationException e){
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public Result<?> handleAuthorizationException(AccessDeniedException e){
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.noauth("没有权限,请联系管理员授权");
|
||||
return Result.noauth("没有权限,请联系管理员授权,后刷新缓存!");
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ -84,6 +135,7 @@ public class JeecgBootExceptionHandler {
|
||||
return Result.error(errorInfoEnum.getError());
|
||||
}
|
||||
//update-end---author:zyf ---date:20220411 for:处理Sentinel限流自定义异常
|
||||
addSysLog(e);
|
||||
return Result.error("操作失败,"+e.getMessage());
|
||||
}
|
||||
|
||||
@ -108,6 +160,7 @@ public class JeecgBootExceptionHandler {
|
||||
}
|
||||
log.error(sb.toString(), e);
|
||||
//return Result.error("没有权限,请联系管理员授权");
|
||||
addSysLog(e);
|
||||
return Result.error(405,sb.toString());
|
||||
}
|
||||
|
||||
@ -117,12 +170,14 @@ public class JeecgBootExceptionHandler {
|
||||
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||
public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
addSysLog(e);
|
||||
return Result.error("文件大小超出10MB限制, 请压缩或降低文件质量! ");
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public Result<?> handleDataIntegrityViolationException(DataIntegrityViolationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
addSysLog(e);
|
||||
//【issues/3624】数据库执行异常handleDataIntegrityViolationException提示有误 #3624
|
||||
return Result.error("执行数据库异常,违反了完整性例如:违反惟一约束、违反非空限制、字段内容超出长度等");
|
||||
}
|
||||
@ -130,7 +185,78 @@ public class JeecgBootExceptionHandler {
|
||||
@ExceptionHandler(PoolException.class)
|
||||
public Result<?> handlePoolException(PoolException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
addSysLog(e);
|
||||
return Result.error("Redis 连接异常!");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SQL注入风险,全局异常处理
|
||||
*
|
||||
* @param exception
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(JeecgSqlInjectionException.class)
|
||||
public Result<?> handleSQLException(Exception exception) {
|
||||
String msg = exception.getMessage().toLowerCase();
|
||||
final String extractvalue = "extractvalue";
|
||||
final String updatexml = "updatexml";
|
||||
boolean hasSensitiveInformation = msg.indexOf(extractvalue) >= 0 || msg.indexOf(updatexml) >= 0;
|
||||
if (msg != null && hasSensitiveInformation) {
|
||||
log.error("校验失败,存在SQL注入风险!{}", msg);
|
||||
return Result.error("校验失败,存在SQL注入风险!");
|
||||
}
|
||||
addSysLog(exception);
|
||||
return Result.error("校验失败,存在SQL注入风险!" + msg);
|
||||
}
|
||||
|
||||
//update-begin---author:chenrui ---date:20240423 for:[QQYUN-8732]把错误的日志都抓取了 方便后续处理,单独弄个日志类型------------
|
||||
/**
|
||||
* 添加异常新系统日志
|
||||
* @param e 异常
|
||||
* @author chenrui
|
||||
* @date 2024/4/22 17:16
|
||||
*/
|
||||
private void addSysLog(Throwable e) {
|
||||
LogDTO log = new LogDTO();
|
||||
log.setLogType(CommonConstant.LOG_TYPE_4);
|
||||
log.setLogContent(e.getClass().getName()+":"+e.getMessage());
|
||||
log.setRequestParam(ExceptionUtils.getStackTrace(e));
|
||||
//获取request
|
||||
HttpServletRequest request = null;
|
||||
try {
|
||||
request = SpringContextUtils.getHttpServletRequest();
|
||||
} catch (NullPointerException | BeansException ignored) {
|
||||
}
|
||||
if (null != request) {
|
||||
//请求的参数
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
if(!CollectionUtils.isEmpty(parameterMap)){
|
||||
log.setMethod(oConvertUtils.mapToString(request.getParameterMap()));
|
||||
}
|
||||
// 请求地址
|
||||
log.setRequestUrl(request.getRequestURI());
|
||||
//设置IP地址
|
||||
log.setIp(IpUtils.getIpAddr(request));
|
||||
//设置客户端
|
||||
if(BrowserUtils.isDesktop(request)){
|
||||
log.setClientType(ClientTerminalTypeEnum.PC.getKey());
|
||||
}else{
|
||||
log.setClientType(ClientTerminalTypeEnum.APP.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//获取登录用户信息
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
if(sysUser!=null){
|
||||
log.setUserid(sysUser.getUsername());
|
||||
log.setUsername(sysUser.getRealname());
|
||||
|
||||
}
|
||||
|
||||
baseCommonService.addLog(log);
|
||||
}
|
||||
//update-end---author:chenrui ---date:20240423 for:[QQYUN-8732]把错误的日志都抓取了 方便后续处理,单独弄个日志类型------------
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package org.jeecg.common.exception;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author kezhijie@wuhandsj.com
|
||||
* @date 2024/1/2 11:38
|
||||
*/
|
||||
@Data
|
||||
public class JeecgCaptchaException extends RuntimeException{
|
||||
|
||||
private Integer code;
|
||||
|
||||
private static final long serialVersionUID = -9093410345065209053L;
|
||||
|
||||
public JeecgCaptchaException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public JeecgCaptchaException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public JeecgCaptchaException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.jeecg.common.exception;
|
||||
|
||||
/**
|
||||
* @Description: jeecg-boot自定义SQL注入异常
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public class JeecgSqlInjectionException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public JeecgSqlInjectionException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
public JeecgSqlInjectionException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public JeecgSqlInjectionException(String message, Throwable cause)
|
||||
{
|
||||
super(message,cause);
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,18 @@
|
||||
package org.jeecg.common.system.base.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.jeecgframework.poi.excel.ExcelImportUtil;
|
||||
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
|
||||
import org.jeecgframework.poi.excel.entity.ExportParams;
|
||||
@ -18,16 +20,16 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
|
||||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @Description: Controller基类
|
||||
@ -40,9 +42,9 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
/**issues/2933 JeecgController注入service时改用protected修饰,能避免重复引用service*/
|
||||
@Autowired
|
||||
protected S service;
|
||||
|
||||
@Value("${jeecg.path.upload}")
|
||||
private String upLoadPath;
|
||||
@Resource
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*
|
||||
@ -51,7 +53,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
protected ModelAndView exportXls(HttpServletRequest request, T object, Class<T> clazz, String title) {
|
||||
// Step.1 组装查询条件
|
||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser sysUser = SecureUtil.currentUser();
|
||||
|
||||
// 过滤选中数据
|
||||
String selections = request.getParameter("selections");
|
||||
@ -68,8 +70,8 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
||||
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
||||
//update-begin--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||
exportParams.setImageBasePath(upLoadPath);
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
|
||||
//update-end--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
|
||||
mv.addObject(NormalExcelConstants.PARAMS,exportParams);
|
||||
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
|
||||
@ -89,7 +91,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
protected ModelAndView exportXlsSheet(HttpServletRequest request, T object, Class<T> clazz, String title,String exportFields,Integer pageNum) {
|
||||
// Step.1 组装查询条件
|
||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
LoginUser sysUser = SecureUtil.currentUser();
|
||||
// Step.2 计算分页sheet数据
|
||||
double total = service.count();
|
||||
int count = (int)Math.ceil(total/pageNum);
|
||||
@ -108,7 +110,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
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 exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
//map.put("title",exportParams);
|
||||
//表格Title
|
||||
|
||||
@ -9,10 +9,10 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* @Description: Entity基类
|
||||
@ -30,20 +30,20 @@ public class JeecgEntity implements Serializable {
|
||||
* ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@ApiModelProperty(value = "ID")
|
||||
@Schema(description = "ID")
|
||||
private java.lang.String id;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@ApiModelProperty(value = "创建人")
|
||||
@Schema(description = "创建人")
|
||||
@Excel(name = "创建人", width = 15)
|
||||
private java.lang.String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@ApiModelProperty(value = "创建时间")
|
||||
@Schema(description = "创建时间")
|
||||
@Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ -52,14 +52,14 @@ public class JeecgEntity implements Serializable {
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@ApiModelProperty(value = "更新人")
|
||||
@Schema(description = "更新人")
|
||||
@Excel(name = "更新人", width = 15)
|
||||
private java.lang.String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@ApiModelProperty(value = "更新时间")
|
||||
@Schema(description = "更新时间")
|
||||
@Excel(name = "更新时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package org.jeecg.common.system.enhance;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户增强
|
||||
*/
|
||||
public interface UserFilterEnhance {
|
||||
|
||||
/**
|
||||
* 获取用户id
|
||||
* @param loginUserId 当前登录的用户id
|
||||
*
|
||||
* @return List<String> 返回多个用户id
|
||||
*/
|
||||
default List<String> getUserIds(String loginUserId) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,14 @@ public class QueryCondition implements Serializable {
|
||||
private String dbType;
|
||||
private String rule;
|
||||
private String val;
|
||||
|
||||
public QueryCondition(String field, String type, String dbType, String rule, String val) {
|
||||
this.field = field;
|
||||
this.type = type;
|
||||
this.dbType = dbType;
|
||||
this.rule = rule;
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
|
||||
@ -2,7 +2,6 @@ package org.jeecg.common.system.query;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLDecoder;
|
||||
import java.text.ParseException;
|
||||
@ -15,19 +14,15 @@ import java.util.stream.Collectors;
|
||||
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.util.SqlConcatUtil;
|
||||
import org.jeecg.common.system.vo.SysPermissionDataRuleModel;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.SqlInjectionUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.util.*;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -96,10 +91,27 @@ public class QueryGenerator {
|
||||
public static <T> QueryWrapper<T> initQueryWrapper(T searchObj,Map<String, String[]> parameterMap){
|
||||
long start = System.currentTimeMillis();
|
||||
QueryWrapper<T> queryWrapper = new QueryWrapper<T>();
|
||||
installMplus(queryWrapper, searchObj, parameterMap);
|
||||
installMplus(queryWrapper, searchObj, parameterMap, null);
|
||||
log.debug("---查询条件构造器初始化完成,耗时:"+(System.currentTimeMillis()-start)+"毫秒----");
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
//update-begin---author:chenrui ---date:20240527 for:[TV360X-378]增加自定义字段查询规则功能------------
|
||||
/**
|
||||
* 获取查询条件构造器QueryWrapper实例 通用查询条件已被封装完成
|
||||
* @param searchObj 查询实体
|
||||
* @param parameterMap request.getParameterMap()
|
||||
* @param customRuleMap 自定义字段查询规则 {field:QueryRuleEnum}
|
||||
* @return QueryWrapper实例
|
||||
*/
|
||||
public static <T> QueryWrapper<T> initQueryWrapper(T searchObj,Map<String, String[]> parameterMap, Map<String, QueryRuleEnum> customRuleMap){
|
||||
long start = System.currentTimeMillis();
|
||||
QueryWrapper<T> queryWrapper = new QueryWrapper<T>();
|
||||
installMplus(queryWrapper, searchObj, parameterMap, customRuleMap);
|
||||
log.debug("---查询条件构造器初始化完成,耗时:"+(System.currentTimeMillis()-start)+"毫秒----");
|
||||
return queryWrapper;
|
||||
}
|
||||
//update-end---author:chenrui ---date:20240527 for:[TV360X-378]增加自定义字段查询规则功能------------
|
||||
|
||||
/**
|
||||
* 组装Mybatis Plus 查询条件
|
||||
@ -110,8 +122,7 @@ public class QueryGenerator {
|
||||
* <br>正确示例:QueryWrapper<JeecgDemo> queryWrapper = new QueryWrapper<JeecgDemo>();
|
||||
* <br>3.也可以不使用这个方法直接调用 {@link #initQueryWrapper}直接获取实例
|
||||
*/
|
||||
private static void installMplus(QueryWrapper<?> queryWrapper,Object searchObj,Map<String, String[]> parameterMap) {
|
||||
|
||||
private static void installMplus(QueryWrapper<?> queryWrapper, Object searchObj, Map<String, String[]> parameterMap, Map<String, QueryRuleEnum> customRuleMap) {
|
||||
/*
|
||||
* 注意:权限查询由前端配置数据规则 当一个人有多个所属部门时候 可以在规则配置包含条件 orgCode 包含 #{sys_org_code}
|
||||
但是不支持在自定义SQL中写orgCode in #{sys_org_code}
|
||||
@ -143,7 +154,7 @@ public class QueryGenerator {
|
||||
}
|
||||
|
||||
Object value = PropertyUtils.getSimpleProperty(searchObj, name);
|
||||
column = getTableFieldName(searchObj.getClass(), name);
|
||||
column = ReflectHelper.getTableFieldName(searchObj.getClass(), name);
|
||||
if(column==null){
|
||||
//column为null只有一种情况 那就是 添加了注解@TableField(exist = false) 后续都不用处理了
|
||||
continue;
|
||||
@ -176,8 +187,16 @@ public class QueryGenerator {
|
||||
queryWrapper.and(j -> j.like(field,vals[0]));
|
||||
}
|
||||
}else {
|
||||
//根据参数值带什么关键字符串判断走什么类型的查询
|
||||
QueryRuleEnum rule = convert2Rule(value);
|
||||
//update-begin---author:chenrui ---date:20240527 for:[TV360X-378]增加自定义字段查询规则功能------------
|
||||
QueryRuleEnum rule;
|
||||
if(null != customRuleMap && customRuleMap.containsKey(name)) {
|
||||
// 有自定义规则,使用自定义规则.
|
||||
rule = customRuleMap.get(name);
|
||||
}else {
|
||||
//根据参数值带什么关键字符串判断走什么类型的查询
|
||||
rule = convert2Rule(value);
|
||||
}
|
||||
//update-end---author:chenrui ---date:20240527 for:[TV360X-378]增加自定义字段查询规则功能------------
|
||||
value = replaceValue(rule,value);
|
||||
// add -begin 添加判断为字符串时设为全模糊查询
|
||||
//if( (rule==null || QueryRuleEnum.EQ.equals(rule)) && "class java.lang.String".equals(type)) {
|
||||
@ -193,7 +212,7 @@ public class QueryGenerator {
|
||||
}
|
||||
}
|
||||
// 排序逻辑 处理
|
||||
doMultiFieldsOrder(queryWrapper, parameterMap, fieldColumnMap.keySet());
|
||||
doMultiFieldsOrder(queryWrapper, parameterMap, fieldColumnMap);
|
||||
|
||||
//高级查询
|
||||
doSuperQuery(queryWrapper, parameterMap, fieldColumnMap);
|
||||
@ -229,7 +248,8 @@ public class QueryGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap, Set<String> allFields) {
|
||||
private static void doMultiFieldsOrder(QueryWrapper<?> queryWrapper,Map<String, String[]> parameterMap, Map<String,String> fieldColumnMap) {
|
||||
Set<String> allFields = fieldColumnMap.keySet();
|
||||
String column=null,order=null;
|
||||
if(parameterMap!=null&& parameterMap.containsKey(ORDER_COLUMN)) {
|
||||
column = parameterMap.get(ORDER_COLUMN)[0];
|
||||
@ -238,6 +258,15 @@ public class QueryGenerator {
|
||||
order = parameterMap.get(ORDER_TYPE)[0];
|
||||
}
|
||||
log.debug("排序规则>>列:" + column + ",排序方式:" + order);
|
||||
|
||||
//update-begin-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
|
||||
//TODO 避免用户自定义表无默认字段创建时间,导致排序报错
|
||||
if(DataBaseConstant.CREATE_TIME.equals(column) && !fieldColumnMap.containsKey(DataBaseConstant.CREATE_TIME)){
|
||||
column = "id";
|
||||
log.warn("检测到实体里没有字段createTime,改成采用ID排序!");
|
||||
}
|
||||
//update-end-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
|
||||
|
||||
if (oConvertUtils.isNotEmpty(column) && oConvertUtils.isNotEmpty(order)) {
|
||||
//字典字段,去掉字典翻译文本后缀
|
||||
if(column.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
|
||||
@ -252,23 +281,30 @@ public class QueryGenerator {
|
||||
}
|
||||
//update-end-author:taoyan date:2022-5-16 for: issues/3676 获取系统用户列表时,使用SQL注入生效
|
||||
|
||||
//update-begin-author:scott date:2022-10-10 for:【jeecg-boot/issues/I5FJU6】doMultiFieldsOrder() 多字段排序方法存在问题
|
||||
//多字段排序方法没有读取 MybatisPlus 注解 @TableField 里 value 的值
|
||||
if (column.contains(",")) {
|
||||
List<String> columnList = Arrays.asList(column.split(","));
|
||||
String columnStrNew = columnList.stream().map(c -> fieldColumnMap.get(c)).collect(Collectors.joining(","));
|
||||
if (oConvertUtils.isNotEmpty(columnStrNew)) {
|
||||
column = columnStrNew;
|
||||
}
|
||||
}else{
|
||||
column = fieldColumnMap.get(column);
|
||||
}
|
||||
//update-end-author:scott date:2022-10-10 for:【jeecg-boot/issues/I5FJU6】doMultiFieldsOrder() 多字段排序方法存在问题
|
||||
|
||||
//SQL注入check
|
||||
SqlInjectionUtil.filterContent(column);
|
||||
SqlInjectionUtil.filterContentMulti(column);
|
||||
|
||||
//update-begin--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
|
||||
// 排序规则修改
|
||||
// 将现有排序 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1,column2 desc"
|
||||
// 修改为 _ 前端传递排序条件{....,column: 'column1,column2',order: 'desc'} 翻译成sql "column1 desc,column2 desc"
|
||||
if (order.toUpperCase().indexOf(ORDER_TYPE_ASC)>=0) {
|
||||
//queryWrapper.orderByAsc(oConvertUtils.camelToUnderline(column));
|
||||
String columnStr = oConvertUtils.camelToUnderline(column);
|
||||
String[] columnArray = columnStr.split(",");
|
||||
queryWrapper.orderByAsc(Arrays.asList(columnArray));
|
||||
queryWrapper.orderByAsc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||
} else {
|
||||
//queryWrapper.orderByDesc(oConvertUtils.camelToUnderline(column));
|
||||
String columnStr = oConvertUtils.camelToUnderline(column);
|
||||
String[] columnArray = columnStr.split(",");
|
||||
queryWrapper.orderByDesc(Arrays.asList(columnArray));
|
||||
queryWrapper.orderByDesc(SqlInjectionUtil.getSqlInjectSortFields(column.split(",")));
|
||||
}
|
||||
//update-end--Author:scott Date:20210531 for:36 多条件排序无效问题修正-------
|
||||
}
|
||||
@ -324,7 +360,7 @@ public class QueryGenerator {
|
||||
return;
|
||||
}
|
||||
// update-end-author:sunjianlei date:20220119 for: 【JTC-573】 过滤空条件查询,防止 sql 拼接多余的 and
|
||||
log.info("---高级查询参数-->" + filterConditions);
|
||||
log.debug("---高级查询参数-->" + filterConditions);
|
||||
|
||||
queryWrapper.and(andWrapper -> {
|
||||
for (int i = 0; i < filterConditions.size(); i++) {
|
||||
@ -618,11 +654,11 @@ public class QueryGenerator {
|
||||
* @param value 查询条件值
|
||||
*/
|
||||
public static void addEasyQuery(QueryWrapper<?> queryWrapper, String name, QueryRuleEnum rule, Object value) {
|
||||
if (value == null || rule == null || oConvertUtils.isEmpty(value)) {
|
||||
if (name==null || value == null || rule == null || oConvertUtils.isEmpty(value)) {
|
||||
return;
|
||||
}
|
||||
name = oConvertUtils.camelToUnderline(name);
|
||||
log.info("---查询过滤器,Query规则---field:{}, rule:{}, value:{}",name,rule.getValue(),value);
|
||||
log.debug("---高级查询 Query规则---field:{} , rule:{} , value:{}",name,rule.getValue(),value);
|
||||
switch (rule) {
|
||||
case GT:
|
||||
queryWrapper.gt(name, value);
|
||||
@ -663,9 +699,40 @@ public class QueryGenerator {
|
||||
case LEFT_LIKE:
|
||||
queryWrapper.likeLeft(name, value);
|
||||
break;
|
||||
case NOT_LEFT_LIKE:
|
||||
queryWrapper.notLikeLeft(name, value);
|
||||
break;
|
||||
case RIGHT_LIKE:
|
||||
queryWrapper.likeRight(name, value);
|
||||
break;
|
||||
case NOT_RIGHT_LIKE:
|
||||
queryWrapper.notLikeRight(name, value);
|
||||
break;
|
||||
//update-begin---author:chenrui ---date:20240527 for:[TV360X-378]下拉多框根据条件查询不出来:增加自定义字段查询规则功能------------
|
||||
case LIKE_WITH_OR:
|
||||
final String nameFinal = name;
|
||||
Object[] vals;
|
||||
if (value instanceof String) {
|
||||
vals = value.toString().split(COMMA);
|
||||
} else if (value instanceof String[]) {
|
||||
vals = (Object[]) value;
|
||||
}
|
||||
//update-begin-author:taoyan date:20200909 for:【bug】in 类型多值查询 不适配postgresql #1671
|
||||
else if (value.getClass().isArray()) {
|
||||
vals = (Object[]) value;
|
||||
} else {
|
||||
vals = new Object[]{value};
|
||||
}
|
||||
queryWrapper.and(j -> {
|
||||
log.info("---查询过滤器,Query规则---field:{}, rule:{}, value:{}", nameFinal, "like", vals[0]);
|
||||
j = j.like(nameFinal, vals[0]);
|
||||
for (int k = 1; k < vals.length; k++) {
|
||||
j = j.or().like(nameFinal, vals[k]);
|
||||
log.info("---查询过滤器,Query规则 .or()---field:{}, rule:{}, value:{}", nameFinal, "like", vals[k]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
//update-end---author:chenrui ---date:20240527 for:[TV360X-378]下拉多框根据条件查询不出来:增加自定义字段查询规则功能------------
|
||||
default:
|
||||
log.info("--查询规则未匹配到---");
|
||||
break;
|
||||
@ -690,7 +757,14 @@ public class QueryGenerator {
|
||||
*/
|
||||
public static Map<String, SysPermissionDataRuleModel> getRuleMap() {
|
||||
Map<String, SysPermissionDataRuleModel> ruleMap = new HashMap<>(5);
|
||||
List<SysPermissionDataRuleModel> list =JeecgDataAutorUtils.loadDataSearchConditon();
|
||||
List<SysPermissionDataRuleModel> list = null;
|
||||
//update-begin-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
||||
try {
|
||||
list = JeecgDataAutorUtils.loadDataSearchConditon();
|
||||
}catch (Exception e){
|
||||
log.error("根据request对象获取权限数据失败,可能是定时任务中执行的。", e);
|
||||
}
|
||||
//update-end-author:taoyan date:2023-6-1 for:QQYUN-5441 【简流】获取多个用户/部门/角色 设置部门查询 报错
|
||||
if(list != null&&list.size()>0){
|
||||
if(list.get(0)==null){
|
||||
return ruleMap;
|
||||
@ -798,223 +872,7 @@ public class QueryGenerator {
|
||||
* @return
|
||||
*/
|
||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString) {
|
||||
return getSingleQueryConditionSql(field, alias, value, isString,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 报表获取查询条件 支持多数据源
|
||||
* @param field
|
||||
* @param alias
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString, String dataBaseType) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
field = alias+oConvertUtils.camelToUnderline(field);
|
||||
QueryRuleEnum rule = QueryGenerator.convert2Rule(value);
|
||||
return getSingleSqlByRule(rule, field, value, isString, dataBaseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个查询条件的值
|
||||
* @param rule
|
||||
* @param field
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
|
||||
String res = "";
|
||||
switch (rule) {
|
||||
case GT:
|
||||
res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case GE:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case LT:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case LE:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case EQ:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case EQ_WITH_ADD:
|
||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case NE:
|
||||
res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case IN:
|
||||
res = field + " in "+getInConditionValue(value, isString);
|
||||
break;
|
||||
case LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
|
||||
break;
|
||||
case LEFT_LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
|
||||
break;
|
||||
case RIGHT_LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
|
||||
break;
|
||||
default:
|
||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取单个查询条件的值
|
||||
* @param rule
|
||||
* @param field
|
||||
* @param value
|
||||
* @param isString
|
||||
* @return
|
||||
*/
|
||||
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString) {
|
||||
return getSingleSqlByRule(rule, field, value, isString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询条件的值
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
private static String getFieldConditionValue(Object value,boolean isString, String dataBaseType) {
|
||||
String str = value.toString().trim();
|
||||
if(str.startsWith(SymbolConstant.EXCLAMATORY_MARK)) {
|
||||
str = str.substring(1);
|
||||
}else if(str.startsWith(QueryRuleEnum.GE.getValue())) {
|
||||
str = str.substring(2);
|
||||
}else if(str.startsWith(QueryRuleEnum.LE.getValue())) {
|
||||
str = str.substring(2);
|
||||
}else if(str.startsWith(QueryRuleEnum.GT.getValue())) {
|
||||
str = str.substring(1);
|
||||
}else if(str.startsWith(QueryRuleEnum.LT.getValue())) {
|
||||
str = str.substring(1);
|
||||
}else if(str.indexOf(QUERY_COMMA_ESCAPE)>0) {
|
||||
str = str.replaceAll("\\+\\+", COMMA);
|
||||
}
|
||||
if(dataBaseType==null){
|
||||
dataBaseType = getDbType();
|
||||
}
|
||||
if(isString) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType)){
|
||||
return " N'"+str+"' ";
|
||||
}else{
|
||||
return " '"+str+"' ";
|
||||
}
|
||||
}else {
|
||||
// 如果不是字符串 有一种特殊情况 popup调用都走这个逻辑 参数传递的可能是“‘admin’”这种格式的
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return " N"+str;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getInConditionValue(Object value,boolean isString) {
|
||||
//update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
||||
String[] temp = value.toString().split(",");
|
||||
if(temp.length==0){
|
||||
return "('')";
|
||||
}
|
||||
if(isString) {
|
||||
List<String> res = new ArrayList<>();
|
||||
for (String string : temp) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
res.add("N'"+string+"'");
|
||||
}else{
|
||||
res.add("'"+string+"'");
|
||||
}
|
||||
}
|
||||
return "("+String.join("," ,res)+")";
|
||||
}else {
|
||||
return "("+value.toString()+")";
|
||||
}
|
||||
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
||||
}
|
||||
|
||||
/**
|
||||
* 先根据值判断 走左模糊还是右模糊
|
||||
* 最后如果值不带任何标识(*或者%),则再根据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())){
|
||||
return "N'%"+str.substring(1,str.length()-1)+"%'";
|
||||
}else{
|
||||
return "'%"+str.substring(1,str.length()-1)+"%'";
|
||||
}
|
||||
}else if(str.startsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'%"+str.substring(1)+"'";
|
||||
}else{
|
||||
return "'%"+str.substring(1)+"'";
|
||||
}
|
||||
}else if(str.endsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'"+str.substring(0,str.length()-1)+"%'";
|
||||
}else{
|
||||
return "'"+str.substring(0,str.length()-1)+"%'";
|
||||
}
|
||||
}else {
|
||||
if(str.indexOf(SymbolConstant.PERCENT_SIGN)>=0) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return "N"+str;
|
||||
}else{
|
||||
return "N"+"'"+str+"'";
|
||||
}
|
||||
}else{
|
||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return str;
|
||||
}else{
|
||||
return "'"+str+"'";
|
||||
}
|
||||
}
|
||||
}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 数据权限规则问题
|
||||
|
||||
}
|
||||
}
|
||||
return SqlConcatUtil.getSingleQueryConditionSql(field, alias, value, isString,null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1041,7 +899,7 @@ public class QueryGenerator {
|
||||
continue;
|
||||
}
|
||||
if(ruleMap.containsKey(name)) {
|
||||
column = getTableFieldName(clazz, name);
|
||||
column = ReflectHelper.getTableFieldName(clazz, name);
|
||||
if(column==null){
|
||||
continue;
|
||||
}
|
||||
@ -1050,12 +908,14 @@ public class QueryGenerator {
|
||||
Class propType = origDescriptors[i].getPropertyType();
|
||||
boolean isString = propType.equals(String.class);
|
||||
Object value;
|
||||
if(isString) {
|
||||
//update-begin---author:chenrui ---date:20240527 for:[TV360X-539]数据权限,配置日期等于条件时后端报转换错误------------
|
||||
if(isString || Date.class.equals(propType)) {
|
||||
//update-end---author:chenrui ---date:20240527 for:[TV360X-539]数据权限,配置日期等于条件时后端报转换错误------------
|
||||
value = converRuleValue(dataRule.getRuleValue());
|
||||
}else {
|
||||
value = NumberUtils.parseNumber(dataRule.getRuleValue(),propType);
|
||||
}
|
||||
String filedSql = getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString);
|
||||
String filedSql = SqlConcatUtil.getSingleSqlByRule(rule, oConvertUtils.camelToUnderline(column), value,isString);
|
||||
sb.append(sqlAnd+filedSql);
|
||||
}
|
||||
}
|
||||
@ -1084,7 +944,7 @@ public class QueryGenerator {
|
||||
if (judgedIsUselessField(name)) {
|
||||
continue;
|
||||
}
|
||||
column = getTableFieldName(clazz, name);
|
||||
column = ReflectHelper.getTableFieldName(clazz, name);
|
||||
if(column==null){
|
||||
continue;
|
||||
}
|
||||
@ -1103,42 +963,6 @@ public class QueryGenerator {
|
||||
return getSqlRuleValue(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置的权限 返回sql字符串 不受字段限制 配置什么就拿到什么
|
||||
* @return
|
||||
*/
|
||||
public static String getAllConfigAuth() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
//权限查询
|
||||
Map<String,SysPermissionDataRuleModel> ruleMap = getRuleMap();
|
||||
String sqlAnd = " and ";
|
||||
for (String c : ruleMap.keySet()) {
|
||||
SysPermissionDataRuleModel dataRule = ruleMap.get(c);
|
||||
String ruleValue = dataRule.getRuleValue();
|
||||
if(oConvertUtils.isEmpty(ruleValue)){
|
||||
continue;
|
||||
}
|
||||
if(oConvertUtils.isNotEmpty(c) && c.startsWith(SQL_RULES_COLUMN)){
|
||||
sb.append(sqlAnd+getSqlRuleValue(ruleValue));
|
||||
}else{
|
||||
boolean isString = false;
|
||||
ruleValue = ruleValue.trim();
|
||||
if(ruleValue.startsWith("'") && ruleValue.endsWith("'")){
|
||||
isString = true;
|
||||
ruleValue = ruleValue.substring(1,ruleValue.length()-1);
|
||||
}
|
||||
QueryRuleEnum rule = QueryRuleEnum.getByValue(dataRule.getRuleConditions());
|
||||
String value = converRuleValue(ruleValue);
|
||||
String filedSql = getSingleSqlByRule(rule, c, value,isString);
|
||||
sb.append(sqlAnd+filedSql);
|
||||
}
|
||||
}
|
||||
log.info("query auth sql is = "+sb.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取系统数据库类型
|
||||
*/
|
||||
@ -1146,71 +970,6 @@ public class QueryGenerator {
|
||||
return CommonUtils.getDatabaseType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取class的 包括父类的
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
private static List<Field> getClassFields(Class<?> clazz) {
|
||||
List<Field> list = new ArrayList<Field>();
|
||||
Field[] fields;
|
||||
do{
|
||||
fields = clazz.getDeclaredFields();
|
||||
for(int i = 0;i<fields.length;i++){
|
||||
list.add(fields[i]);
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
}while(clazz!= Object.class&&clazz!=null);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表字段名
|
||||
* @param clazz
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
private static String getTableFieldName(Class<?> clazz, String name) {
|
||||
try {
|
||||
//如果字段加注解了@TableField(exist = false),不走DB查询
|
||||
Field field = null;
|
||||
try {
|
||||
field = clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
|
||||
//如果为空,则去父类查找字段
|
||||
if (field == null) {
|
||||
List<Field> allFields = getClassFields(clazz);
|
||||
List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
|
||||
if(searchFields!=null && searchFields.size()>0){
|
||||
field = searchFields.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (field != null) {
|
||||
TableField tableField = field.getAnnotation(TableField.class);
|
||||
if (tableField != null){
|
||||
if(tableField.exist() == false){
|
||||
//如果设置了TableField false 这个字段不需要处理
|
||||
return null;
|
||||
}else{
|
||||
String column = tableField.value();
|
||||
//如果设置了TableField value 这个字段是实体字段
|
||||
if(!"".equals(column)){
|
||||
return column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* mysql 模糊查询之特殊字符下划线 (_、\)
|
||||
*
|
||||
|
||||
@ -25,16 +25,44 @@ public enum QueryRuleEnum {
|
||||
IN("IN","in","包含"),
|
||||
/**查询规则 全模糊*/
|
||||
LIKE("LIKE","like","全模糊"),
|
||||
/**查询规则 不模糊包含*/
|
||||
NOT_LIKE("NOT_LIKE","not_like","不模糊包含"),
|
||||
/**查询规则 左模糊*/
|
||||
LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
|
||||
/**查询规则 右模糊*/
|
||||
RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
|
||||
/**查询规则 带加号等于*/
|
||||
EQ_WITH_ADD("EQWITHADD","eq_with_add","带加号等于"),
|
||||
/**查询规则 多词模糊匹配*/
|
||||
/**查询规则 多词模糊匹配(and)*/
|
||||
LIKE_WITH_AND("LIKEWITHAND","like_with_and","多词模糊匹配————暂时未用上"),
|
||||
/**查询规则 多词模糊匹配(or)*/
|
||||
LIKE_WITH_OR("LIKEWITHOR","like_with_or","多词模糊匹配(or)"),
|
||||
/**查询规则 自定义SQL片段*/
|
||||
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段");
|
||||
SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段"),
|
||||
|
||||
/** 查询工作表 */
|
||||
LINKAGE("LINKAGE","linkage","查询工作表"),
|
||||
|
||||
// ------- 当前表单设计器内专用 -------
|
||||
/**查询规则 不以…结尾*/
|
||||
NOT_LEFT_LIKE("NOT_LEFT_LIKE","not_left_like","不以…结尾"),
|
||||
/**查询规则 不以…开头*/
|
||||
NOT_RIGHT_LIKE("NOT_RIGHT_LIKE","not_right_like","不以…开头"),
|
||||
/** 值为空 */
|
||||
EMPTY("EMPTY","empty","值为空"),
|
||||
/** 值不为空 */
|
||||
NOT_EMPTY("NOT_EMPTY","not_empty","值不为空"),
|
||||
/**查询规则 不包含*/
|
||||
NOT_IN("NOT_IN","not_in","不包含"),
|
||||
/**查询规则 多词匹配*/
|
||||
ELE_MATCH("ELE_MATCH","elemMatch","多词匹配"),
|
||||
/**查询规则 范围查询*/
|
||||
RANGE("RANGE","range","范围查询"),
|
||||
/**查询规则 不在范围内查询*/
|
||||
NOT_RANGE("NOT_RANGE","not_range","不在范围查询"),
|
||||
/** 自定义mongodb查询语句 */
|
||||
CUSTOM_MONGODB("CUSTOM_MONGODB","custom_mongodb","自定义mongodb查询语句");
|
||||
// ------- 当前表单设计器内专用 -------
|
||||
|
||||
private String value;
|
||||
|
||||
@ -77,7 +105,7 @@ public enum QueryRuleEnum {
|
||||
return null;
|
||||
}
|
||||
for(QueryRuleEnum val :values()){
|
||||
if (val.getValue().equals(value) || val.getCondition().equals(value)){
|
||||
if (val.getValue().equals(value) || val.getCondition().equalsIgnoreCase(value)){
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import org.jeecg.common.system.vo.SysUserCacheInfo;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package org.jeecg.common.system.util;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
@ -10,35 +12,59 @@ import com.google.common.base.Joiner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.CommonAPI;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.DataBaseConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.constant.TenantConstant;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.system.vo.SysUserCacheInfo;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.security.self.SelfAuthenticationProvider;
|
||||
import org.jeecg.config.security.self.SelfAuthenticationToken;
|
||||
import org.jeecg.config.security.utils.SecureUtil;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
|
||||
/**
|
||||
* @Author Scott
|
||||
* @Date 2018-07-12 14:23
|
||||
* @Desc JWT工具类
|
||||
**/
|
||||
@Slf4j
|
||||
public class JwtUtil {
|
||||
|
||||
/**Token有效期为1小时(Token在reids中缓存时间为两倍)*/
|
||||
public static final long EXPIRE_TIME = 60 * 60 * 1000;
|
||||
/**Token有效期为7天(Token在reids中缓存时间为两倍)*/
|
||||
public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000;
|
||||
static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
|
||||
|
||||
public static final String DEFAULT_CLIENT = "jeecg-client";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param response
|
||||
@ -50,6 +76,7 @@ public class JwtUtil {
|
||||
// issues/I4YH95浏览器显示乱码问题
|
||||
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
|
||||
Result jsonResult = new Result(code, errorMsg);
|
||||
jsonResult.setSuccess(false);
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = httpServletResponse.getOutputStream();
|
||||
@ -73,10 +100,9 @@ public class JwtUtil {
|
||||
public static boolean verify(String token, String username, String secret) {
|
||||
try {
|
||||
// 根据密码生成JWT效验器
|
||||
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
|
||||
JwtDecoder jwtDecoder = SpringContextUtils.getBean(JwtDecoder.class);
|
||||
// 效验TOKEN
|
||||
DecodedJWT jwt = verifier.verify(token);
|
||||
jwtDecoder.decode(token);
|
||||
return true;
|
||||
} catch (Exception exception) {
|
||||
return false;
|
||||
@ -91,24 +117,33 @@ public class JwtUtil {
|
||||
public static String getUsername(String token) {
|
||||
try {
|
||||
DecodedJWT jwt = JWT.decode(token);
|
||||
return jwt.getClaim("username").asString();
|
||||
LoginUser loginUser = JSONObject.parseObject(jwt.getClaim("sub").asString(), LoginUser.class);
|
||||
return loginUser.getUsername();
|
||||
} catch (JWTDecodeException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名,5min后过期
|
||||
* 生成token
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param secret 用户的密码
|
||||
* @return 加密的token
|
||||
*/
|
||||
public static String sign(String username, String secret) {
|
||||
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
|
||||
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||
// 附带username信息
|
||||
return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
|
||||
Map<String, Object> additionalParameter = new HashMap<>();
|
||||
additionalParameter.put("username", username);
|
||||
|
||||
RegisteredClientRepository registeredClientRepository = SpringContextUtils.getBean(RegisteredClientRepository.class);
|
||||
SelfAuthenticationProvider selfAuthenticationProvider = SpringContextUtils.getBean(SelfAuthenticationProvider.class);
|
||||
|
||||
OAuth2ClientAuthenticationToken client = new OAuth2ClientAuthenticationToken(Objects.requireNonNull(registeredClientRepository.findByClientId("jeecg-client")), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null);
|
||||
client.setAuthenticated(true);
|
||||
SelfAuthenticationToken selfAuthenticationToken = new SelfAuthenticationToken(client, additionalParameter);
|
||||
selfAuthenticationToken.setAuthenticated(true);
|
||||
OAuth2AccessTokenAuthenticationToken accessToken = (OAuth2AccessTokenAuthenticationToken) selfAuthenticationProvider.authenticate(selfAuthenticationToken);
|
||||
return accessToken.getAccessToken().getTokenValue();
|
||||
|
||||
}
|
||||
|
||||
@ -161,15 +196,24 @@ public class JwtUtil {
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public static String getUserSystemData(String key,SysUserCacheInfo user) {
|
||||
public static String getUserSystemData(String key, SysUserCacheInfo user) {
|
||||
//1.优先获取 SysUserCacheInfo
|
||||
if(user==null) {
|
||||
user = JeecgDataAutorUtils.loadUserInfo();
|
||||
try {
|
||||
user = JeecgDataAutorUtils.loadUserInfo();
|
||||
} catch (Exception e) {
|
||||
log.warn("获取用户信息异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
//2.通过shiro获取登录用户信息
|
||||
LoginUser sysUser = null;
|
||||
try {
|
||||
sysUser = SecureUtil.currentUser();
|
||||
} catch (Exception e) {
|
||||
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
//#{sys_user_code}%
|
||||
|
||||
// 获取登录用户信息
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
|
||||
String moshi = "";
|
||||
String wellNumber = WELL_NUMBER;
|
||||
if(key.indexOf(SymbolConstant.RIGHT_CURLY_BRACKET)!=-1){
|
||||
@ -182,6 +226,24 @@ public class JwtUtil {
|
||||
} else {
|
||||
key = key;
|
||||
}
|
||||
//替换为当前系统时间(年月日)
|
||||
if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
||||
returnValue = DateUtils.formatDate();
|
||||
}
|
||||
//替换为当前系统时间(年月日时分秒)
|
||||
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
|
||||
returnValue = DateUtils.now();
|
||||
}
|
||||
//流程状态默认值(默认未发起)
|
||||
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
|
||||
returnValue = "1";
|
||||
}
|
||||
|
||||
//后台任务获取用户信息异常,导致程序中断
|
||||
if(sysUser==null && user==null){
|
||||
return null;
|
||||
}
|
||||
|
||||
//替换为系统登录用户帐号
|
||||
if (key.equals(DataBaseConstant.SYS_USER_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_CODE_TABLE)) {
|
||||
if(user==null) {
|
||||
@ -190,6 +252,16 @@ public class JwtUtil {
|
||||
returnValue = user.getSysUserCode();
|
||||
}
|
||||
}
|
||||
|
||||
// 替换为系统登录用户ID
|
||||
else if (key.equals(DataBaseConstant.SYS_USER_ID) || key.equalsIgnoreCase(DataBaseConstant.SYS_USER_ID_TABLE)) {
|
||||
if(user==null) {
|
||||
returnValue = sysUser.getId();
|
||||
}else {
|
||||
returnValue = user.getSysUserId();
|
||||
}
|
||||
}
|
||||
|
||||
//替换为系统登录用户真实名字
|
||||
else if (key.equals(DataBaseConstant.SYS_USER_NAME)|| key.toLowerCase().equals(DataBaseConstant.SYS_USER_NAME_TABLE)) {
|
||||
if(user==null) {
|
||||
@ -207,6 +279,16 @@ public class JwtUtil {
|
||||
returnValue = user.getSysOrgCode();
|
||||
}
|
||||
}
|
||||
|
||||
// 替换为系统用户登录所使用的机构ID
|
||||
else if (key.equals(DataBaseConstant.SYS_ORG_ID) || key.equalsIgnoreCase(DataBaseConstant.SYS_ORG_ID_TABLE)) {
|
||||
if (user == null) {
|
||||
returnValue = sysUser.getOrgId();
|
||||
} else {
|
||||
returnValue = user.getSysOrgId();
|
||||
}
|
||||
}
|
||||
|
||||
//替换为系统用户所拥有的所有机构编码
|
||||
else if (key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE)|| key.toLowerCase().equals(DataBaseConstant.SYS_MULTI_ORG_CODE_TABLE)) {
|
||||
if(user==null){
|
||||
@ -220,24 +302,22 @@ public class JwtUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
//替换为当前系统时间(年月日)
|
||||
else if (key.equals(DataBaseConstant.SYS_DATE)|| key.toLowerCase().equals(DataBaseConstant.SYS_DATE_TABLE)) {
|
||||
returnValue = DateUtils.formatDate();
|
||||
}
|
||||
//替换为当前系统时间(年月日时分秒)
|
||||
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.toLowerCase().equals(DataBaseConstant.SYS_TIME_TABLE)) {
|
||||
returnValue = DateUtils.now();
|
||||
}
|
||||
//流程状态默认值(默认未发起)
|
||||
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.toLowerCase().equals(DataBaseConstant.BPM_STATUS_TABLE)) {
|
||||
returnValue = "1";
|
||||
|
||||
// 替换为当前登录用户的角色code(多个逗号分割)
|
||||
else if (key.equals(DataBaseConstant.SYS_ROLE_CODE) || key.equalsIgnoreCase(DataBaseConstant.SYS_ROLE_CODE_TABLE)) {
|
||||
if (user == null) {
|
||||
returnValue = sysUser.getRoleCode();
|
||||
} else {
|
||||
returnValue = user.getSysRoleCode();
|
||||
}
|
||||
}
|
||||
|
||||
//update-begin-author:taoyan date:20210330 for:多租户ID作为系统变量
|
||||
else if (key.equals(DataBaseConstant.TENANT_ID) || key.toLowerCase().equals(DataBaseConstant.TENANT_ID_TABLE)){
|
||||
returnValue = sysUser.getRelTenantIds();
|
||||
boolean flag = returnValue != null && returnValue.indexOf(SymbolConstant.COMMA) > 0;
|
||||
if(oConvertUtils.isEmpty(returnValue) || flag){
|
||||
else if (key.equals(TenantConstant.TENANT_ID) || key.toLowerCase().equals(TenantConstant.TENANT_ID_TABLE)){
|
||||
try {
|
||||
returnValue = SpringContextUtils.getHttpServletRequest().getHeader(CommonConstant.TENANT_ID);
|
||||
} catch (Exception e) {
|
||||
log.warn("获取系统租户异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:20210330 for:多租户ID作为系统变量
|
||||
|
||||
@ -3,7 +3,9 @@ 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.SpringContextUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
@ -36,6 +38,12 @@ public class ResourceUtil {
|
||||
*/
|
||||
private final static String CLASS_PATTERN="/**/*.class";
|
||||
|
||||
/**
|
||||
* 所有枚举java类
|
||||
*/
|
||||
|
||||
private final static String CLASS_ENUM_PATTERN="/**/*Enum.class";
|
||||
|
||||
/**
|
||||
* 包路径 org.jeecg
|
||||
*/
|
||||
@ -55,7 +63,7 @@ public class ResourceUtil {
|
||||
return enumDictData;
|
||||
}
|
||||
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
|
||||
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_PATTERN;
|
||||
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + CLASS_ENUM_PATTERN;
|
||||
try {
|
||||
Resource[] resources = resourcePatternResolver.getResources(pattern);
|
||||
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
|
||||
@ -108,4 +116,21 @@ public class ResourceUtil {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实现类
|
||||
*
|
||||
* @param classPath
|
||||
*/
|
||||
public static Object getImplementationClass(String classPath){
|
||||
try {
|
||||
Class<?> aClass = Class.forName(classPath);
|
||||
return SpringContextUtils.getBean(aClass);
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("类没有找到",e);
|
||||
return null;
|
||||
} catch (NoSuchBeanDefinitionException e){
|
||||
log.error(classPath + "没有实现",e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,243 @@
|
||||
package org.jeecg.common.system.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.DataBaseConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.query.QueryRuleEnum;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: 查询过滤器,SQL拼接写法拆成独立工具类
|
||||
* @author:qinfeng
|
||||
* @date 20230904
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlConcatUtil {
|
||||
|
||||
/**
|
||||
* 获取单个查询条件的值
|
||||
* @param rule
|
||||
* @param field
|
||||
* @param value
|
||||
* @param isString
|
||||
* @return
|
||||
*/
|
||||
public static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString) {
|
||||
return getSingleSqlByRule(rule, field, value, isString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 报表获取查询条件 支持多数据源
|
||||
* @param field
|
||||
* @param alias
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
public static String getSingleQueryConditionSql(String field,String alias,Object value,boolean isString, String dataBaseType) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
field = alias+oConvertUtils.camelToUnderline(field);
|
||||
QueryRuleEnum rule = QueryGenerator.convert2Rule(value);
|
||||
return getSingleSqlByRule(rule, field, value, isString, dataBaseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个查询条件的值
|
||||
* @param rule
|
||||
* @param field
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
private static String getSingleSqlByRule(QueryRuleEnum rule,String field,Object value,boolean isString, String dataBaseType) {
|
||||
String res = "";
|
||||
switch (rule) {
|
||||
case GT:
|
||||
res =field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case GE:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case LT:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case LE:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case EQ:
|
||||
res = field+rule.getValue()+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case EQ_WITH_ADD:
|
||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case NE:
|
||||
res = field+" <> "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
case IN:
|
||||
res = field + " in "+getInConditionValue(value, isString);
|
||||
break;
|
||||
case LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LIKE);
|
||||
break;
|
||||
case LEFT_LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.LEFT_LIKE);
|
||||
break;
|
||||
case RIGHT_LIKE:
|
||||
res = field + " like "+getLikeConditionValue(value, QueryRuleEnum.RIGHT_LIKE);
|
||||
break;
|
||||
default:
|
||||
res = field+" = "+getFieldConditionValue(value, isString, dataBaseType);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询条件的值
|
||||
* @param value
|
||||
* @param isString
|
||||
* @param dataBaseType
|
||||
* @return
|
||||
*/
|
||||
private static String getFieldConditionValue(Object value,boolean isString, String dataBaseType) {
|
||||
String str = value.toString().trim();
|
||||
if(str.startsWith(SymbolConstant.EXCLAMATORY_MARK)) {
|
||||
str = str.substring(1);
|
||||
}else if(str.startsWith(QueryRuleEnum.GE.getValue())) {
|
||||
str = str.substring(2);
|
||||
}else if(str.startsWith(QueryRuleEnum.LE.getValue())) {
|
||||
str = str.substring(2);
|
||||
}else if(str.startsWith(QueryRuleEnum.GT.getValue())) {
|
||||
str = str.substring(1);
|
||||
}else if(str.startsWith(QueryRuleEnum.LT.getValue())) {
|
||||
str = str.substring(1);
|
||||
}else if(str.indexOf(QueryGenerator.QUERY_COMMA_ESCAPE)>0) {
|
||||
str = str.replaceAll("\\+\\+", SymbolConstant.COMMA);
|
||||
}
|
||||
if(dataBaseType==null){
|
||||
dataBaseType = getDbType();
|
||||
}
|
||||
if(isString) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType)){
|
||||
return " N'"+str+"' ";
|
||||
}else{
|
||||
return " '"+str+"' ";
|
||||
}
|
||||
}else {
|
||||
// 如果不是字符串 有一种特殊情况 popup调用都走这个逻辑 参数传递的可能是“‘admin’”这种格式的
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(dataBaseType) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return " N"+str;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getInConditionValue(Object value,boolean isString) {
|
||||
//update-begin-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
||||
String[] temp = value.toString().split(",");
|
||||
if(temp.length==0){
|
||||
return "('')";
|
||||
}
|
||||
if(isString) {
|
||||
List<String> res = new ArrayList<>();
|
||||
for (String string : temp) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
res.add("N'"+string+"'");
|
||||
}else{
|
||||
res.add("'"+string+"'");
|
||||
}
|
||||
}
|
||||
return "("+String.join("," ,res)+")";
|
||||
}else {
|
||||
return "("+value.toString()+")";
|
||||
}
|
||||
//update-end-author:taoyan date:20210628 for: 查询条件如果输入,导致sql报错
|
||||
}
|
||||
|
||||
/**
|
||||
* 先根据值判断 走左模糊还是右模糊
|
||||
* 最后如果值不带任何标识(*或者%),则再根据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())){
|
||||
return "N'%"+str.substring(1,str.length()-1)+"%'";
|
||||
}else{
|
||||
return "'%"+str.substring(1,str.length()-1)+"%'";
|
||||
}
|
||||
}else if(str.startsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'%"+str.substring(1)+"'";
|
||||
}else{
|
||||
return "'%"+str.substring(1)+"'";
|
||||
}
|
||||
}else if(str.endsWith(SymbolConstant.ASTERISK)) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
return "N'"+str.substring(0,str.length()-1)+"%'";
|
||||
}else{
|
||||
return "'"+str.substring(0,str.length()-1)+"%'";
|
||||
}
|
||||
}else {
|
||||
if(str.indexOf(SymbolConstant.PERCENT_SIGN)>=0) {
|
||||
if(DataBaseConstant.DB_TYPE_SQLSERVER.equals(getDbType())){
|
||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return "N"+str;
|
||||
}else{
|
||||
return "N"+"'"+str+"'";
|
||||
}
|
||||
}else{
|
||||
if(str.startsWith(SymbolConstant.SINGLE_QUOTATION_MARK) && str.endsWith(SymbolConstant.SINGLE_QUOTATION_MARK)){
|
||||
return str;
|
||||
}else{
|
||||
return "'"+str+"'";
|
||||
}
|
||||
}
|
||||
}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 数据权限规则问题
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统数据库类型
|
||||
*/
|
||||
private static String getDbType() {
|
||||
return CommonUtils.getDatabaseType();
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,6 +2,7 @@ package org.jeecg.common.system.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import lombok.Data;
|
||||
@ -26,7 +27,13 @@ public class DictModel implements Serializable{
|
||||
this.value = value;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
||||
public DictModel(String value, String text, String color) {
|
||||
this.value = value;
|
||||
this.text = text;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典value
|
||||
*/
|
||||
@ -35,6 +42,10 @@ public class DictModel implements Serializable{
|
||||
* 字典文本
|
||||
*/
|
||||
private String text;
|
||||
/**
|
||||
* 字典颜色
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 特殊用途: JgEditableTable
|
||||
@ -50,4 +61,11 @@ public class DictModel implements Serializable{
|
||||
return this.text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用于表单设计器 关联记录表数据存储
|
||||
* QQYUN-5595【表单设计器】他表字段 导入没有翻译
|
||||
*/
|
||||
private JSONObject jsonObject;
|
||||
|
||||
}
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
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.alibaba.fastjson2.JSON;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -22,8 +25,10 @@ import lombok.experimental.Accessors;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class LoginUser {
|
||||
public class LoginUser implements Serializable {
|
||||
|
||||
|
||||
private static final long serialVersionUID = -7143159031677245866L;
|
||||
/**
|
||||
* 登录人id
|
||||
*/
|
||||
@ -51,7 +56,19 @@ public class LoginUser {
|
||||
/**
|
||||
* 当前登录部门code
|
||||
*/
|
||||
@SensitiveField
|
||||
private String orgCode;
|
||||
/**
|
||||
* 当前登录部门id
|
||||
*/
|
||||
@SensitiveField
|
||||
private String orgId;
|
||||
/**
|
||||
* 当前登录角色code(多个逗号分割)
|
||||
*/
|
||||
@SensitiveField
|
||||
private String roleCode;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
@ -61,7 +78,6 @@ public class LoginUser {
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
@SensitiveField
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date birthday;
|
||||
@ -107,6 +123,7 @@ public class LoginUser {
|
||||
/**
|
||||
* 管理部门ids
|
||||
*/
|
||||
@SensitiveField
|
||||
private String departIds;
|
||||
|
||||
/**
|
||||
@ -121,10 +138,36 @@ public class LoginUser {
|
||||
@SensitiveField
|
||||
private String telephone;
|
||||
|
||||
/**多租户id配置,编辑用户的时候设置*/
|
||||
/** 多租户ids临时用,不持久化数据库(数据库字段不存在) */
|
||||
@SensitiveField
|
||||
private String relTenantIds;
|
||||
|
||||
/**设备id uniapp推送用*/
|
||||
private String clientId;
|
||||
|
||||
@SensitiveField
|
||||
private String salt;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// 重新构建对象过滤一些敏感字段
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setId(id);
|
||||
loginUser.setUsername(username);
|
||||
loginUser.setRealname(realname);
|
||||
loginUser.setOrgCode(orgCode);
|
||||
loginUser.setSex(sex);
|
||||
loginUser.setEmail(email);
|
||||
loginUser.setPhone(phone);
|
||||
loginUser.setDelFlag(delFlag);
|
||||
loginUser.setStatus(status);
|
||||
loginUser.setActivitiSync(activitiSync);
|
||||
loginUser.setUserIdentity(userIdentity);
|
||||
loginUser.setDepartIds(departIds);
|
||||
loginUser.setPost(post);
|
||||
loginUser.setTelephone(telephone);
|
||||
loginUser.setRelTenantIds(relTenantIds);
|
||||
loginUser.setClientId(clientId);
|
||||
return JSON.toJSONString(loginUser);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ public class SysFilesModel {
|
||||
private String storeType;
|
||||
/**文件大小(kb)*/
|
||||
private Double fileSize;
|
||||
/**租户id*/
|
||||
private String tenantId;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
@ -67,4 +69,12 @@ public class SysFilesModel {
|
||||
public void setFileSize(Double fileSize) {
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public String getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(String tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
}
|
||||
@ -9,17 +9,29 @@ import org.jeecg.common.util.DateUtils;
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public class SysUserCacheInfo {
|
||||
|
||||
|
||||
private String sysUserId;
|
||||
|
||||
private String sysUserCode;
|
||||
|
||||
private String sysUserName;
|
||||
|
||||
private String sysOrgCode;
|
||||
|
||||
|
||||
/**
|
||||
* 当前用户部门ID
|
||||
*/
|
||||
private String sysOrgId;
|
||||
|
||||
private List<String> sysMultiOrgCode;
|
||||
|
||||
private boolean oneDepart;
|
||||
|
||||
|
||||
/**
|
||||
* 当前用户角色code(多个逗号分割)
|
||||
*/
|
||||
private String sysRoleCode;
|
||||
|
||||
public boolean isOneDepart() {
|
||||
return oneDepart;
|
||||
}
|
||||
@ -68,4 +80,27 @@ public class SysUserCacheInfo {
|
||||
this.sysMultiOrgCode = sysMultiOrgCode;
|
||||
}
|
||||
|
||||
public String getSysUserId() {
|
||||
return sysUserId;
|
||||
}
|
||||
|
||||
public void setSysUserId(String sysUserId) {
|
||||
this.sysUserId = sysUserId;
|
||||
}
|
||||
|
||||
public String getSysOrgId() {
|
||||
return sysOrgId;
|
||||
}
|
||||
|
||||
public void setSysOrgId(String sysOrgId) {
|
||||
this.sysOrgId = sysOrgId;
|
||||
}
|
||||
|
||||
public String getSysRoleCode() {
|
||||
return sysRoleCode;
|
||||
}
|
||||
|
||||
public void setSysRoleCode(String sysRoleCode) {
|
||||
this.sysRoleCode = sysRoleCode;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
package org.jeecg.common.system.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 在线用户信息
|
||||
* </p>
|
||||
*
|
||||
* @Author scott
|
||||
* @since 2023-08-16
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class UserAccountInfo {
|
||||
|
||||
/**
|
||||
* 登录人id
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 登录人账号
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 登录人名字
|
||||
*/
|
||||
private String realname;
|
||||
|
||||
/**
|
||||
* 电子邮件
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
@SensitiveField
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 同步工作流引擎1同步0不同步
|
||||
*/
|
||||
private Integer activitiSync;
|
||||
|
||||
/**
|
||||
* 电话
|
||||
*/
|
||||
@SensitiveField
|
||||
private String phone;
|
||||
}
|
||||
@ -5,7 +5,7 @@ import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -1,22 +1,25 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
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.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
import org.jeecg.common.util.oss.OssBootUtil;
|
||||
import org.jeecgframework.poi.util.PoiPublicUtil;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import javax.sql.DataSource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
@ -25,7 +28,9 @@ import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -135,6 +140,7 @@ public class CommonUtils {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new JeecgBootException(e.getMessage());
|
||||
}
|
||||
return url;
|
||||
}
|
||||
@ -147,7 +153,7 @@ public class CommonUtils {
|
||||
public static String uploadLocal(MultipartFile mf,String bizPath,String uploadpath){
|
||||
try {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(mf);
|
||||
SsrfFileTypeFilter.checkUploadFileType(mf);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
String fileName = null;
|
||||
File file = new File(uploadpath + File.separator + bizPath + File.separator );
|
||||
@ -298,7 +304,7 @@ public class CommonUtils {
|
||||
DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE;
|
||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_SQLSERVER)>=0||dbType.indexOf(sqlserver)>=0) {
|
||||
DB_TYPE = DataBaseConstant.DB_TYPE_SQLSERVER;
|
||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_POSTGRESQL)>=0) {
|
||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_POSTGRESQL)>=0 || dbType.indexOf(DataBaseConstant.DB_TYPE_KINGBASEES)>=0) {
|
||||
DB_TYPE = DataBaseConstant.DB_TYPE_POSTGRESQL;
|
||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_MARIADB)>=0) {
|
||||
DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB;
|
||||
@ -342,8 +348,11 @@ public class CommonUtils {
|
||||
|
||||
//返回 host domain
|
||||
String baseDomainPath = null;
|
||||
int length = 80;
|
||||
if(length == serverPort){
|
||||
//update-begin---author:wangshuai---date:2024-03-15---for:【QQYUN-8561】企业微信登陆请求接口设置上下文不一致,导致接口404---
|
||||
int httpPort = 80;
|
||||
int httpsPort = 443;
|
||||
if(httpPort == serverPort || httpsPort == serverPort){
|
||||
//update-end---author:wangshuai---date:2024-03-15---for:【QQYUN-8561】企业微信登陆请求接口设置上下文不一致,导致接口404---~
|
||||
baseDomainPath = scheme + "://" + serverName + contextPath ;
|
||||
}else{
|
||||
baseDomainPath = scheme + "://" + serverName + ":" + serverPort + contextPath ;
|
||||
@ -351,4 +360,131 @@ public class CommonUtils {
|
||||
log.debug("-----Common getBaseUrl----- : " + baseDomainPath);
|
||||
return baseDomainPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归合并 fastJSON 对象
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param sources 来源对象,允许多个,优先级从左到右,最右侧的优先级最高
|
||||
*/
|
||||
public static JSONObject mergeJSON(JSONObject target, JSONObject... sources) {
|
||||
for (JSONObject source : sources) {
|
||||
CommonUtils.mergeJSON(target, source);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归合并 fastJSON 对象
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param source 来源对象
|
||||
*/
|
||||
public static JSONObject mergeJSON(JSONObject target, JSONObject source) {
|
||||
for (String key : source.keySet()) {
|
||||
Object sourceItem = source.get(key);
|
||||
// 是否是 JSONObject
|
||||
if (sourceItem instanceof Map) {
|
||||
// target中存在此key
|
||||
if (target.containsKey(key)) {
|
||||
// 两个都是 JSONObject,继续合并
|
||||
if (target.get(key) instanceof Map) {
|
||||
CommonUtils.mergeJSON(target.getJSONObject(key), source.getJSONObject(key));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// target不存在此key,或不是 JSONObject,则覆盖
|
||||
target.put(key, sourceItem);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将list集合以分割符的方式进行分割
|
||||
* @param list String类型的集合文本
|
||||
* @param separator 分隔符
|
||||
* @return
|
||||
*/
|
||||
public static String getSplitText(List<String> list, String separator) {
|
||||
if (null != list && list.size() > 0) {
|
||||
return StringUtils.join(list, separator);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过table的条件SQL
|
||||
*
|
||||
* @param tableSql sys_user where name = '1212'
|
||||
* @return name = '1212'
|
||||
*/
|
||||
public static String getFilterSqlByTableSql(String tableSql) {
|
||||
if(oConvertUtils.isEmpty(tableSql)){
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tableSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE) > 0) {
|
||||
String[] arr = tableSql.split(" (?i)where ");
|
||||
if (arr != null && oConvertUtils.isNotEmpty(arr[1])) {
|
||||
return arr[1];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过table获取表名
|
||||
*
|
||||
* @param tableSql sys_user where name = '1212'
|
||||
* @return sys_user
|
||||
*/
|
||||
public static String getTableNameByTableSql(String tableSql) {
|
||||
if(oConvertUtils.isEmpty(tableSql)){
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tableSql.toLowerCase().indexOf(DataBaseConstant.SQL_WHERE) > 0) {
|
||||
String[] arr = tableSql.split(" (?i)where ");
|
||||
return arr[0].trim();
|
||||
} else {
|
||||
return tableSql;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个数组是否存在交集
|
||||
* @param set1
|
||||
* @param arr2
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasIntersection(Set<String> set1, String[] arr2) {
|
||||
if (set1 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(set1.size()>0){
|
||||
for (String str : arr2) {
|
||||
if (set1.contains(str)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出info日志,会捕获异常,防止因为日志问题导致程序异常
|
||||
*
|
||||
* @param msg
|
||||
* @param objects
|
||||
*/
|
||||
public static void logInfo(String msg, Object... objects) {
|
||||
try {
|
||||
log.info(msg, objects);
|
||||
} catch (Exception e) {
|
||||
log.warn("{} —— {}", msg, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,242 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import org.jeecg.common.constant.enums.DateRangeEnum;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 日期范围工具类
|
||||
*
|
||||
* @author scott
|
||||
* @date 20230801
|
||||
*/
|
||||
public class DateRangeUtils {
|
||||
|
||||
/**
|
||||
* 根据日期范围枚举获取日期范围
|
||||
*
|
||||
* @param rangeEnum
|
||||
* @return Date[]
|
||||
*/
|
||||
public static Date[] getDateRangeByEnum(DateRangeEnum rangeEnum) {
|
||||
if (rangeEnum == null) {
|
||||
return null;
|
||||
}
|
||||
Date[] ranges = new Date[2];
|
||||
switch (rangeEnum) {
|
||||
case TODAY:
|
||||
ranges[0] = getTodayStartTime();
|
||||
ranges[1] = getTodayEndTime();
|
||||
break;
|
||||
case YESTERDAY:
|
||||
ranges[0] = getYesterdayStartTime();
|
||||
ranges[1] = getYesterdayEndTime();
|
||||
break;
|
||||
case TOMORROW:
|
||||
ranges[0] = getTomorrowStartTime();
|
||||
ranges[1] = getTomorrowEndTime();
|
||||
break;
|
||||
case THIS_WEEK:
|
||||
ranges[0] = getThisWeekStartDay();
|
||||
ranges[1] = getThisWeekEndDay();
|
||||
break;
|
||||
case LAST_WEEK:
|
||||
ranges[0] = getLastWeekStartDay();
|
||||
ranges[1] = getLastWeekEndDay();
|
||||
break;
|
||||
case NEXT_WEEK:
|
||||
ranges[0] = getNextWeekStartDay();
|
||||
ranges[1] = getNextWeekEndDay();
|
||||
break;
|
||||
case LAST_7_DAYS:
|
||||
ranges[0] = getLast7DaysStartTime();
|
||||
ranges[1] = getLast7DaysEndTime();
|
||||
break;
|
||||
case THIS_MONTH:
|
||||
ranges[0] = getThisMonthStartDay();
|
||||
ranges[1] = getThisMonthEndDay();
|
||||
break;
|
||||
case LAST_MONTH:
|
||||
ranges[0] = getLastMonthStartDay();
|
||||
ranges[1] = getLastMonthEndDay();
|
||||
break;
|
||||
case NEXT_MONTH:
|
||||
ranges[0] = getNextMonthStartDay();
|
||||
ranges[1] = getNextMonthEndDay();
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得下月第一天 周日 00:00:00
|
||||
*/
|
||||
public static Date getNextMonthStartDay() {
|
||||
return DateUtil.beginOfMonth(DateUtil.nextMonth());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得下月最后一天 23:59:59
|
||||
*/
|
||||
public static Date getNextMonthEndDay() {
|
||||
return DateUtil.endOfMonth(DateUtil.nextMonth());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得本月第一天 周日 00:00:00
|
||||
*/
|
||||
public static Date getThisMonthStartDay() {
|
||||
return DateUtil.beginOfMonth(DateUtil.date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得本月最后一天 23:59:59
|
||||
*/
|
||||
public static Date getThisMonthEndDay() {
|
||||
return DateUtil.endOfMonth(DateUtil.date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得上月第一天 周日 00:00:00
|
||||
*/
|
||||
public static Date getLastMonthStartDay() {
|
||||
return DateUtil.beginOfMonth(DateUtil.lastMonth());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得上月最后一天 23:59:59
|
||||
*/
|
||||
public static Date getLastMonthEndDay() {
|
||||
return DateUtil.endOfMonth(DateUtil.lastMonth());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得上周第一天 周一 00:00:00
|
||||
*/
|
||||
public static Date getLastWeekStartDay() {
|
||||
return DateUtil.beginOfWeek(DateUtil.lastWeek());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得上周最后一天 周日 23:59:59
|
||||
*/
|
||||
public static Date getLastWeekEndDay() {
|
||||
return DateUtil.endOfWeek(DateUtil.lastWeek());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得本周第一天 周一 00:00:00
|
||||
*/
|
||||
public static Date getThisWeekStartDay() {
|
||||
Date today = new Date();
|
||||
return DateUtil.beginOfWeek(today);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得本周最后一天 周日 23:59:59
|
||||
*/
|
||||
public static Date getThisWeekEndDay() {
|
||||
Date today = new Date();
|
||||
return DateUtil.endOfWeek(today);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得下周第一天 周一 00:00:00
|
||||
*/
|
||||
public static Date getNextWeekStartDay() {
|
||||
return DateUtil.beginOfWeek(DateUtil.nextWeek());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得下周最后一天 周日 23:59:59
|
||||
*/
|
||||
public static Date getNextWeekEndDay() {
|
||||
return DateUtil.endOfWeek(DateUtil.nextWeek());
|
||||
}
|
||||
|
||||
/**
|
||||
* 过去七天开始时间(不含今天)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Date getLast7DaysStartTime() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(new Date());
|
||||
calendar.add(Calendar.DATE, -7);
|
||||
return DateUtil.beginOfDay(calendar.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 过去七天结束时间(不含今天)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Date getLast7DaysEndTime() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(getLast7DaysStartTime());
|
||||
calendar.add(Calendar.DATE, 6);
|
||||
return DateUtil.endOfDay(calendar.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 昨天开始时间
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Date getYesterdayStartTime() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(new Date());
|
||||
calendar.add(Calendar.DATE, -1);
|
||||
return DateUtil.beginOfDay(calendar.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 昨天结束时间
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Date getYesterdayEndTime() {
|
||||
return DateUtil.endOfDay(getYesterdayStartTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 明天开始时间
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Date getTomorrowStartTime() {
|
||||
return DateUtil.beginOfDay(DateUtil.tomorrow());
|
||||
}
|
||||
|
||||
/**
|
||||
* 明天结束时间
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Date getTomorrowEndTime() {
|
||||
return DateUtil.endOfDay(DateUtil.tomorrow());
|
||||
}
|
||||
|
||||
/**
|
||||
* 今天开始时间
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Date getTodayStartTime() {
|
||||
return DateUtil.beginOfDay(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 今天结束时间
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Date getTodayEndTime() {
|
||||
return DateUtil.endOfDay(new Date());
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,17 +1,22 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.beans.PropertyEditorSupport;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* 类描述:时间操作定义类
|
||||
*
|
||||
@ -116,6 +121,17 @@ public class DateUtils extends PropertyEditorSupport {
|
||||
public static Date getDate() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 当前日期
|
||||
*
|
||||
* @return 系统当前日期(不带时分秒)
|
||||
*/
|
||||
public static LocalDate getLocalDate() {
|
||||
LocalDate today = LocalDate.now();
|
||||
return today;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定毫秒数表示的日期
|
||||
@ -684,4 +700,118 @@ public class DateUtils extends PropertyEditorSupport {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个时间是否是同一天
|
||||
*
|
||||
* @param date1
|
||||
* @param date2
|
||||
* @return
|
||||
*/
|
||||
public static boolean isSameDay(Date date1, Date date2) {
|
||||
if (date1 == null || date2 == null) {
|
||||
return false;
|
||||
}
|
||||
Calendar calendar1 = Calendar.getInstance();
|
||||
calendar1.setTime(date1);
|
||||
Calendar calendar2 = Calendar.getInstance();
|
||||
calendar2.setTime(date2);
|
||||
boolean isSameYear = calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
|
||||
boolean isSameMonth = isSameYear && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
|
||||
return isSameMonth && calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算与当前日期的时间差
|
||||
*
|
||||
* @param targetDate
|
||||
* @return
|
||||
*/
|
||||
public static long calculateTimeDifference(Date targetDate) {
|
||||
// 获取当前时间
|
||||
LocalDateTime currentTime = LocalDateTime.now();
|
||||
|
||||
// 将java.util.Date转换为java.time.LocalDateTime
|
||||
LocalDateTime convertedTargetDate = targetDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
|
||||
// 计算时间差
|
||||
Duration duration = Duration.between(currentTime, convertedTargetDate);
|
||||
|
||||
// 获取时间差的毫秒数
|
||||
long timeDifferenceInMillis = duration.toMillis();
|
||||
|
||||
return timeDifferenceInMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算与当前日期的日期天数差
|
||||
*
|
||||
* @param targetDate
|
||||
* @return
|
||||
*/
|
||||
public static long calculateDaysDifference(Date targetDate) {
|
||||
// 获取当前日期
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
// 将java.util.Date转换为java.time.LocalDate
|
||||
LocalDate convertedTargetDate = targetDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
// 计算日期差
|
||||
long daysDifference = ChronoUnit.DAYS.between(currentDate, convertedTargetDate);
|
||||
return daysDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个时间是否是同一周
|
||||
*
|
||||
* @param date1
|
||||
* @param date2
|
||||
* @return
|
||||
*/
|
||||
public static boolean isSameWeek(Date date1, Date date2) {
|
||||
if (date1 == null || date2 == null) {
|
||||
return false;
|
||||
}
|
||||
Calendar calendar1 = Calendar.getInstance();
|
||||
calendar1.setTime(date1);
|
||||
Calendar calendar2 = Calendar.getInstance();
|
||||
calendar2.setTime(date2);
|
||||
boolean isSameYear = calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
|
||||
return isSameYear && calendar1.get(Calendar.WEEK_OF_YEAR) == calendar2.get(Calendar.WEEK_OF_YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个时间是否是同一月
|
||||
*
|
||||
* @param date1
|
||||
* @param date2
|
||||
* @return
|
||||
*/
|
||||
public static boolean isSameMonth(Date date1, Date date2) {
|
||||
if (date1 == null || date2 == null) {
|
||||
return false;
|
||||
}
|
||||
Calendar calendar1 = Calendar.getInstance();
|
||||
calendar1.setTime(date1);
|
||||
Calendar calendar2 = Calendar.getInstance();
|
||||
calendar2.setTime(date2);
|
||||
boolean isSameYear = calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
|
||||
return isSameYear && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个时间是否是同一年
|
||||
*
|
||||
* @param date1
|
||||
* @param date2
|
||||
* @return
|
||||
*/
|
||||
public static boolean isSameYear(Date date1, Date date2) {
|
||||
if (date1 == null || date2 == null) {
|
||||
return false;
|
||||
}
|
||||
Calendar calendar1 = Calendar.getInstance();
|
||||
calendar1.setTime(date1);
|
||||
Calendar calendar2 = Calendar.getInstance();
|
||||
calendar2.setTime(date2);
|
||||
return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +1,5 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import org.jeecg.config.StaticConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
@ -12,6 +8,10 @@ import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
|
||||
import com.aliyuncs.exceptions.ClientException;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
import com.aliyuncs.profile.IClientProfile;
|
||||
import org.jeecg.common.constant.enums.DySmsEnum;
|
||||
import org.jeecg.config.StaticConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Created on 17/6/7.
|
||||
@ -55,13 +55,15 @@ public class DySmsHelper {
|
||||
}
|
||||
|
||||
|
||||
public static boolean sendSms(String phone,JSONObject templateParamJson,DySmsEnum dySmsEnum) throws ClientException {
|
||||
public static boolean sendSms(String phone, JSONObject templateParamJson, DySmsEnum dySmsEnum) throws ClientException {
|
||||
//可自助调整超时时间
|
||||
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
|
||||
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
|
||||
|
||||
//update-begin-author:taoyan date:20200811 for:配置类数据获取
|
||||
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
||||
//logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
|
||||
//logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
|
||||
setAccessKeyId(staticConfig.getAccessKeyId());
|
||||
setAccessKeySecret(staticConfig.getAccessKeySecret());
|
||||
//update-end-author:taoyan date:20200811 for:配置类数据获取
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 防止刷短信接口(只针对绑定手机号模板:SMS_175430166)
|
||||
*
|
||||
* 1、同一IP,1分钟内发短信不允许超过5次(每一分钟重置每个IP请求次数)
|
||||
* 2、同一IP,1分钟内发短信超过20次,进入黑名单,不让使用短信接口
|
||||
*
|
||||
* 3、短信接口加签和时间戳
|
||||
* 涉及接口:
|
||||
* /sys/sms
|
||||
* /desform/api/sendVerifyCode
|
||||
* /sys/sendChangePwdSms
|
||||
*/
|
||||
@Slf4j
|
||||
public class DySmsLimit {
|
||||
|
||||
// 1分钟内最大发短信数量(单一IP)
|
||||
private static final int MAX_MESSAGE_PER_MINUTE = 5;
|
||||
// 1分钟
|
||||
private static final int MILLIS_PER_MINUTE = 60000;
|
||||
// 一分钟内报警线最大短信数量,超了进黑名单(单一IP)
|
||||
private static final int MAX_TOTAL_MESSAGE_PER_MINUTE = 20;
|
||||
|
||||
private static ConcurrentHashMap<String, Long> ipLastRequestTime = new ConcurrentHashMap<>();
|
||||
private static ConcurrentHashMap<String, Integer> ipRequestCount = new ConcurrentHashMap<>();
|
||||
private static ConcurrentHashMap<String, Boolean> ipBlacklist = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* @param ip 请求发短信的IP地址
|
||||
* @return
|
||||
*/
|
||||
public static boolean canSendSms(String ip) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long lastRequestTime = ipLastRequestTime.getOrDefault(ip, 0L);
|
||||
int requestCount = ipRequestCount.getOrDefault(ip, 0);
|
||||
log.info("IP:{}, Msg requestCount:{} ", ip, requestCount);
|
||||
|
||||
if (ipBlacklist.getOrDefault(ip, false)) {
|
||||
// 如果IP在黑名单中,则禁止发送短信
|
||||
log.error("IP:{}, 进入黑名单,禁止发送请求短信!", ip);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentTime - lastRequestTime >= MILLIS_PER_MINUTE) {
|
||||
// 如果距离上次请求已经超过一分钟,则重置计数
|
||||
ipRequestCount.put(ip, 1);
|
||||
ipLastRequestTime.put(ip, currentTime);
|
||||
return true;
|
||||
} else {
|
||||
// 如果距离上次请求不到一分钟
|
||||
ipRequestCount.put(ip, requestCount + 1);
|
||||
if (requestCount < MAX_MESSAGE_PER_MINUTE) {
|
||||
// 如果请求次数小于5次,允许发送短信
|
||||
return true;
|
||||
} else if (requestCount >= MAX_TOTAL_MESSAGE_PER_MINUTE) {
|
||||
// 如果请求次数超过报警线短信数量,将IP加入黑名单
|
||||
ipBlacklist.put(ip, true);
|
||||
return false;
|
||||
} else {
|
||||
log.error("IP:{}, 1分钟内请求短信超过5次,请稍后重试!", ip);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片二维码验证成功之后清空数量
|
||||
*
|
||||
* @param ip IP地址
|
||||
*/
|
||||
public static void clearSendSmsCount(String ip) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
ipRequestCount.put(ip, 0);
|
||||
ipLastRequestTime.put(ip, currentTime);
|
||||
}
|
||||
|
||||
// public static void main(String[] args) {
|
||||
// String ip = "192.168.1.1";
|
||||
// for (int i = 1; i < 50; i++) {
|
||||
// if (canSendSms(ip)) {
|
||||
// System.out.println("Send SMS successfully");
|
||||
// } else {
|
||||
// //System.out.println("Exceed SMS limit for IP " + ip);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// System.out.println(ipLastRequestTime);
|
||||
// System.out.println(ipRequestCount);
|
||||
// System.out.println(ipBlacklist);
|
||||
// }
|
||||
}
|
||||
@ -1,12 +1,17 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* IP地址
|
||||
*
|
||||
@ -45,15 +50,52 @@ public class IpUtils {
|
||||
} catch (Exception e) {
|
||||
logger.error("IPUtils ERROR ", e);
|
||||
}
|
||||
|
||||
// //使用代理,则获取第一个IP地址
|
||||
// if(StringUtils.isEmpty(ip) && ip.length() > 15) {
|
||||
// if(ip.indexOf(",") > 0) {
|
||||
// ip = ip.substring(0, ip.indexOf(","));
|
||||
// }
|
||||
// }
|
||||
|
||||
//logger.info("获取客户端 ip:{} ", ip);
|
||||
// 使用代理,则获取第一个IP地址
|
||||
if (StringUtils.isNotEmpty(ip) && ip.length() > 15) {
|
||||
if (ip.indexOf(",") > 0) {
|
||||
//ip = ip.substring(0, ip.indexOf(","));
|
||||
String[] ipAddresses = ip.split(",");
|
||||
for (String ipAddress : ipAddresses) {
|
||||
ipAddress = ipAddress.trim();
|
||||
if (isValidIpAddress(ipAddress)) {
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否是IP格式
|
||||
* @param ipAddress
|
||||
* @return
|
||||
*/
|
||||
public static boolean isValidIpAddress(String ipAddress) {
|
||||
String ipPattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
|
||||
Pattern pattern = Pattern.compile(ipPattern);
|
||||
Matcher matcher = pattern.matcher(ipAddress);
|
||||
return matcher.matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器上的ip
|
||||
* @return
|
||||
*/
|
||||
public static String getServerIp(){
|
||||
InetAddress inetAddress = null;
|
||||
try {
|
||||
inetAddress = InetAddress.getLocalHost();
|
||||
String ipAddress = inetAddress.getHostAddress();
|
||||
//System.out.println("IP地址: " + ipAddress);
|
||||
return ipAddress;
|
||||
} catch (UnknownHostException e) {
|
||||
logger.error("获取ip地址失败", e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import io.minio.*;
|
||||
import io.minio.http.Method;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.util.filter.FileTypeFilter;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@ -60,7 +60,7 @@ public class MinioUtil {
|
||||
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
|
||||
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(file);
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
|
||||
String newBucket = bucketName;
|
||||
|
||||
@ -21,7 +21,7 @@ public class PmsUtil {
|
||||
|
||||
private static String uploadPath;
|
||||
|
||||
@Value("${jeecg.path.upload}")
|
||||
@Value("${jeecg.path.upload:}")
|
||||
public void setUploadPath(String uploadPath) {
|
||||
PmsUtil.uploadPath = uploadPath;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -7,6 +8,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author 张代浩
|
||||
@ -252,4 +254,86 @@ public class ReflectHelper {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定的字段是不是类中的属性
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取class的 包括父类的
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
public static List<Field> getClassFields(Class<?> clazz) {
|
||||
List<Field> list = new ArrayList<Field>();
|
||||
Field[] fields;
|
||||
do{
|
||||
fields = clazz.getDeclaredFields();
|
||||
for(int i = 0;i<fields.length;i++){
|
||||
list.add(fields[i]);
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
}while(clazz!= Object.class&&clazz!=null);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表字段名
|
||||
* @param clazz
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public static String getTableFieldName(Class<?> clazz, String name) {
|
||||
try {
|
||||
//如果字段加注解了@TableField(exist = false),不走DB查询
|
||||
Field field = null;
|
||||
try {
|
||||
field = clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
|
||||
//如果为空,则去父类查找字段
|
||||
if (field == null) {
|
||||
List<Field> allFields = getClassFields(clazz);
|
||||
List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
|
||||
if(searchFields!=null && searchFields.size()>0){
|
||||
field = searchFields.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (field != null) {
|
||||
TableField tableField = field.getAnnotation(TableField.class);
|
||||
if (tableField != null){
|
||||
if(tableField.exist() == false){
|
||||
//如果设置了TableField false 这个字段不需要处理
|
||||
return null;
|
||||
}else{
|
||||
String column = tableField.value();
|
||||
//如果设置了TableField value 这个字段是实体字段
|
||||
if(!"".equals(column)){
|
||||
return column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,7 +3,6 @@ package org.jeecg.common.util;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
@ -22,8 +21,9 @@ import java.util.Map;
|
||||
public class RestUtil {
|
||||
|
||||
private static String domain = null;
|
||||
|
||||
public static String getDomain() {
|
||||
private static String path = null;
|
||||
|
||||
private static String getDomain() {
|
||||
if (domain == null) {
|
||||
domain = SpringContextUtils.getDomain();
|
||||
// issues/2959
|
||||
@ -37,9 +37,7 @@ public class RestUtil {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public static String path = null;
|
||||
|
||||
public static String getPath() {
|
||||
private static String getPath() {
|
||||
if (path == null) {
|
||||
path = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("server.servlet.context-path");
|
||||
}
|
||||
@ -47,18 +45,7 @@ public class RestUtil {
|
||||
}
|
||||
|
||||
public static String getBaseUrl() {
|
||||
String basepath = null;
|
||||
try {
|
||||
basepath = getDomain() + getPath();
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage(),e);
|
||||
}
|
||||
|
||||
//定时任务情况下,通过request是获取不到domain的,这种情况下通过配置获取pc后台域名
|
||||
if(oConvertUtils.isEmpty(basepath)){
|
||||
JeecgBaseConfig jeecgBaseConfig = SpringContextUtils.getBean(JeecgBaseConfig.class);
|
||||
basepath = jeecgBaseConfig.getDomainUrl().getPc();
|
||||
}
|
||||
String basepath = getDomain() + getPath();
|
||||
log.info(" RestUtil.getBaseUrl: " + basepath);
|
||||
return basepath;
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.ServiceNameConstants;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@ -56,12 +57,19 @@ public class SpringContextUtils implements ApplicationContextAware {
|
||||
public static String getDomain(){
|
||||
HttpServletRequest request = getHttpServletRequest();
|
||||
StringBuffer url = request.getRequestURL();
|
||||
//微服务情况下,获取gateway的basePath
|
||||
//1.微服务情况下,获取gateway的basePath
|
||||
String basePath = request.getHeader(ServiceNameConstants.X_GATEWAY_BASE_PATH);
|
||||
if(oConvertUtils.isNotEmpty(basePath)){
|
||||
return basePath;
|
||||
}else{
|
||||
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
|
||||
String domain = url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
|
||||
//2.【兼容】SSL认证之后,request.getScheme()获取不到https的问题
|
||||
// https://blog.csdn.net/weixin_34376986/article/details/89767950
|
||||
String scheme = request.getHeader(CommonConstant.X_FORWARDED_SCHEME);
|
||||
if(scheme!=null && !request.getScheme().equals(scheme)){
|
||||
domain = domain.replace(request.getScheme(),scheme);
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
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 org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@ -14,51 +16,74 @@ import java.util.regex.Pattern;
|
||||
* @author zhoujf
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlInjectionUtil {
|
||||
public class SqlInjectionUtil {
|
||||
/**
|
||||
* sign 用于表字典加签的盐值【SQL漏洞】
|
||||
* (上线修改值 20200501,同步修改前端的盐值)
|
||||
* 默认—sql注入关键词
|
||||
*/
|
||||
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()";
|
||||
|
||||
private final static String XSS_STR = "and |exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|--";
|
||||
/**
|
||||
* 正则 user() 匹配更严谨
|
||||
* online报表专用—sql注入关键词
|
||||
*/
|
||||
private final static String REGULAR_EXPRE_USER = "user[\\s]*\\([\\s]*\\)";
|
||||
/**正则 show tables*/
|
||||
private final static String SHOW_TABLES = "show\\s+tables";
|
||||
|
||||
private static String specialReportXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |alter |delete |grant |update |drop |master |truncate |declare |--";
|
||||
/**
|
||||
* 针对表字典进行额外的sign签名校验(增加安全机制)
|
||||
* @param dictCode:
|
||||
* @param sign:
|
||||
* @param request:
|
||||
* @Return: void
|
||||
* 字典专用—sql注入关键词
|
||||
*/
|
||||
public static void checkDictTableSign(String dictCode, String sign, HttpServletRequest request) {
|
||||
//表字典SQL注入漏洞,签名校验
|
||||
String accessToken = request.getHeader("X-Access-Token");
|
||||
String signStr = dictCode + SqlInjectionUtil.TABLE_DICT_SIGN_SALT + accessToken;
|
||||
String javaSign = SecureUtil.md5(signStr);
|
||||
if (!javaSign.equals(sign)) {
|
||||
log.error("表字典,SQL注入漏洞签名校验失败 :" + sign + "!=" + javaSign+ ",dictCode=" + dictCode);
|
||||
throw new JeecgBootException("无权限访问!");
|
||||
}
|
||||
log.info(" 表字典,SQL注入漏洞签名校验成功!sign=" + sign + ",dictCode=" + dictCode);
|
||||
private static String specialDictSqlXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|--";
|
||||
/**
|
||||
* 完整匹配的key,不需要考虑前空格
|
||||
*/
|
||||
private static List<String> FULL_MATCHING_KEYWRODS = new ArrayList<>();
|
||||
static {
|
||||
FULL_MATCHING_KEYWRODS.add(";");
|
||||
FULL_MATCHING_KEYWRODS.add("+");
|
||||
FULL_MATCHING_KEYWRODS.add("--");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sql注入风险的 正则关键字
|
||||
*
|
||||
* 函数匹配,需要用正则模式
|
||||
*/
|
||||
private final static String[] XSS_REGULAR_STR_ARRAY = new String[]{
|
||||
"chr\\s*\\(",
|
||||
"mid\\s*\\(",
|
||||
" char\\s*\\(",
|
||||
"sleep\\s*\\(",
|
||||
"user\\s*\\(",
|
||||
"show\\s+tables",
|
||||
"user[\\s]*\\([\\s]*\\)",
|
||||
"show\\s+databases",
|
||||
"sleep\\(\\d*\\)",
|
||||
"sleep\\(.*\\)",
|
||||
};
|
||||
/**
|
||||
* sql注释的正则
|
||||
*/
|
||||
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
|
||||
private final static String SQL_ANNOTATION2 = "--";
|
||||
|
||||
/**
|
||||
* sql注入提示语
|
||||
*/
|
||||
private final static String SQL_INJECTION_KEYWORD_TIP = "请注意,存在SQL注入关键词---> {}";
|
||||
private final static String SQL_INJECTION_TIP = "请注意,值可能存在SQL注入风险!--->";
|
||||
private final static String SQL_INJECTION_TIP_VARIABLE = "请注意,值可能存在SQL注入风险!---> {}";
|
||||
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
* @param value
|
||||
* @param values
|
||||
*/
|
||||
public static void filterContent(String value) {
|
||||
filterContent(value, null);
|
||||
public static void filterContentMulti(String... values) {
|
||||
filterContent(values, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
* 校验比较严格
|
||||
*
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
@ -66,45 +91,83 @@ public class SqlInjectionUtil {
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
value = value.replaceAll("/\\*.*\\*/","");
|
||||
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
// 转为小写进行后续比较
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
//https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
String[] xssArr = XSS_STR.split("\\|");
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-7-13 for: 除了XSS_STR这些提前设置好的,还需要额外的校验比如 单引号
|
||||
// 三、SQL注入检测存在绕过风险 (自定义传入普通文本校验)
|
||||
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);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr2[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + 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);
|
||||
|
||||
// 四、SQL注入检测存在绕过风险 (正则校验)
|
||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||
String regular = ".*" + regularOriginal + ".*";
|
||||
if (Pattern.matches(regular, value)) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
* @param values
|
||||
* 判断是否存在SQL注入关键词字符串
|
||||
*
|
||||
* @param keyword
|
||||
* @return
|
||||
*/
|
||||
public static void filterContent(String[] values) {
|
||||
filterContent(values, null);
|
||||
@SuppressWarnings("AlibabaUndefineMagicConstant")
|
||||
private static boolean isExistSqlInjectKeyword(String sql, String keyword) {
|
||||
if (sql.startsWith(keyword.trim())) {
|
||||
return true;
|
||||
} else if (sql.contains(keyword)) {
|
||||
// 需要匹配的,sql注入关键词
|
||||
String matchingText = " " + keyword;
|
||||
if(FULL_MATCHING_KEYWRODS.contains(keyword)){
|
||||
matchingText = keyword;
|
||||
}
|
||||
|
||||
if (sql.contains(matchingText)) {
|
||||
return true;
|
||||
} else {
|
||||
String regularStr = "\\s+\\S+" + keyword;
|
||||
List<String> resultFindAll = ReUtil.findAll(regularStr, sql, 0, new ArrayList<String>());
|
||||
for (String res : resultFindAll) {
|
||||
log.info("isExistSqlInjectKeyword —- 匹配到的SQL注入关键词:{}", res);
|
||||
/**
|
||||
* SQL注入中可以替换空格的字符(%09 %0A %0D +都可以替代空格)
|
||||
* http://blog.chinaunix.net/uid-12501104-id-2932639.html
|
||||
* https://www.cnblogs.com/Vinson404/p/7253255.html
|
||||
* */
|
||||
if (res.contains("%") || res.contains("+") || res.contains("#") || res.contains("/") || res.contains(")")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
@ -112,38 +175,11 @@ public class SqlInjectionUtil {
|
||||
* @return
|
||||
*/
|
||||
public static void filterContent(String[] values, String customXssString) {
|
||||
String[] xssArr = XSS_STR.split("\\|");
|
||||
for (String value : values) {
|
||||
if (value == null || "".equals(value)) {
|
||||
for (String val : values) {
|
||||
if (oConvertUtils.isEmpty(val)) {
|
||||
return;
|
||||
}
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
value = value.replaceAll("/\\*.*\\*/","");
|
||||
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new RuntimeException("请注意,值可能存在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) {
|
||||
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);
|
||||
}
|
||||
filterContent(val, customXssString);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -155,105 +191,238 @@ public class SqlInjectionUtil {
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
//@Deprecated
|
||||
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("\\|");
|
||||
String[] xssArr = specialDictSqlXssStr.split("\\|");
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
value = value.replaceAll("/\\*.*\\*/","");
|
||||
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
if (isExistSqlInjectKeyword(value, xssArr[i])) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
|
||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
|
||||
// 三、SQL注入检测存在绕过风险 (正则校验)
|
||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||
String regular = ".*" + regularOriginal + ".*";
|
||||
if (Pattern.matches(regular, value)) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 【提醒:不通用】
|
||||
* 仅用于Online报表SQL解析,注入过滤
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
//@Deprecated
|
||||
public static void specialFilterContentForOnlineReport(String value) {
|
||||
String specialXssStr = " exec | insert | delete | update | drop | chr | mid | master | truncate | char | declare |user()";
|
||||
String[] xssArr = specialXssStr.split("\\|");
|
||||
String[] xssArr = specialReportXssStr.split("\\|");
|
||||
if (value == null || "".equals(value)) {
|
||||
return;
|
||||
}
|
||||
// 统一转为小写
|
||||
value = value.toLowerCase();
|
||||
//SQL注入检测存在绕过风险 https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
|
||||
value = value.replaceAll("/\\*.*\\*/","");
|
||||
|
||||
// 一、校验sql注释 不允许有sql注释
|
||||
checkSqlAnnotation(value);
|
||||
value = value.toLowerCase().trim();
|
||||
|
||||
// 二、SQL注入检测存在绕过风险 (普通文本校验)
|
||||
for (int i = 0; i < xssArr.length; i++) {
|
||||
if (value.indexOf(xssArr[i]) > -1 || value.startsWith(xssArr[i].trim())) {
|
||||
log.error("请注意,存在SQL注入关键词---> {}", xssArr[i]);
|
||||
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
|
||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
if (isExistSqlInjectKeyword(value, xssArr[i])) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
|
||||
if(Pattern.matches(SHOW_TABLES, value) || Pattern.matches(REGULAR_EXPRE_USER, value)){
|
||||
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
|
||||
// 三、SQL注入检测存在绕过风险 (正则校验)
|
||||
for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
|
||||
String regular = ".*" + regularOriginal + ".*";
|
||||
if (Pattern.matches(regular, value)) {
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
|
||||
log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
|
||||
throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断给定的字段是不是类中的属性
|
||||
* @param field 字段名
|
||||
* @param clazz 类对象
|
||||
* 校验是否有sql注释
|
||||
* @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;
|
||||
}
|
||||
public static void checkSqlAnnotation(String str){
|
||||
if(str.contains(SQL_ANNOTATION2)){
|
||||
String error = "请注意,SQL中不允许含注释,有安全风险!";
|
||||
log.error(error);
|
||||
throw new RuntimeException(error);
|
||||
}
|
||||
return false;
|
||||
|
||||
|
||||
Matcher matcher = SQL_ANNOTATION.matcher(str);
|
||||
if(matcher.find()){
|
||||
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
||||
log.error(error);
|
||||
throw new JeecgSqlInjectionException(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回查询表名
|
||||
* <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) {
|
||||
if(oConvertUtils.isEmpty(table)){
|
||||
return table;
|
||||
}
|
||||
|
||||
//update-begin---author:scott ---date:2024-05-28 for:表单设计器列表翻译存在表名带条件,导致翻译出问题----
|
||||
int index = table.toLowerCase().indexOf(" where ");
|
||||
if (index != -1) {
|
||||
table = table.substring(0, index);
|
||||
log.info("截掉where之后的新表名:" + table);
|
||||
}
|
||||
//update-end---author:scott ---date::2024-05-28 for:表单设计器列表翻译存在表名带条件,导致翻译出问题----
|
||||
|
||||
table = table.trim();
|
||||
/**
|
||||
* 检验表名是否合法
|
||||
*
|
||||
* 表名只能由字母、数字和下划线组成。
|
||||
* 表名必须以字母开头。
|
||||
* 表名长度通常有限制,例如最多为 64 个字符。
|
||||
*/
|
||||
boolean isValidTableName = tableNamePattern.matcher(table).matches();
|
||||
if (!isValidTableName) {
|
||||
String errorMsg = "表名不合法,存在SQL注入风险!--->" + table;
|
||||
log.error(errorMsg);
|
||||
throw new JeecgSqlInjectionException(errorMsg);
|
||||
}
|
||||
|
||||
//进一步验证是否存在SQL注入风险
|
||||
filterContentMulti(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回查询字段
|
||||
* <p>
|
||||
* sql注入过滤处理,遇到注入关键字抛异常
|
||||
*
|
||||
* @param field
|
||||
*/
|
||||
static final Pattern fieldPattern = Pattern.compile("^[a-zA-Z0-9_]+$");
|
||||
public static String getSqlInjectField(String field) {
|
||||
if(oConvertUtils.isEmpty(field)){
|
||||
return field;
|
||||
}
|
||||
|
||||
field = field.trim();
|
||||
|
||||
if (field.contains(SymbolConstant.COMMA)) {
|
||||
return getSqlInjectField(field.split(SymbolConstant.COMMA));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验表字段是否有效
|
||||
*
|
||||
* 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
|
||||
*/
|
||||
boolean isValidField = fieldPattern.matcher(field).matches();
|
||||
if (!isValidField) {
|
||||
String errorMsg = "字段不合法,存在SQL注入风险!--->" + field;
|
||||
log.error(errorMsg);
|
||||
throw new JeecgSqlInjectionException(errorMsg);
|
||||
}
|
||||
|
||||
//进一步验证是否存在SQL注入风险
|
||||
filterContentMulti(field);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定的多个字段是不是类中的属性
|
||||
* @param fieldSet 字段名set
|
||||
* @param clazz 类对象
|
||||
* 获取多个字段
|
||||
* 返回: 逗号拼接
|
||||
*
|
||||
* @param fields
|
||||
* @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;
|
||||
}
|
||||
public static String getSqlInjectField(String... fields) {
|
||||
for (String s : fields) {
|
||||
getSqlInjectField(s);
|
||||
}
|
||||
return true;
|
||||
return String.join(SymbolConstant.COMMA, fields);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取排序字段
|
||||
* 返回:字符串
|
||||
*
|
||||
* 1.将驼峰命名转化成下划线
|
||||
* 2.限制sql注入
|
||||
* @param sortField 排序字段
|
||||
* @return
|
||||
*/
|
||||
public static String getSqlInjectSortField(String sortField) {
|
||||
String field = SqlInjectionUtil.getSqlInjectField(oConvertUtils.camelToUnderline(sortField));
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多个排序字段
|
||||
* 返回:数组
|
||||
*
|
||||
* 1.将驼峰命名转化成下划线
|
||||
* 2.限制sql注入
|
||||
* @param sortFields 多个排序字段
|
||||
* @return
|
||||
*/
|
||||
public static List getSqlInjectSortFields(String... sortFields) {
|
||||
List list = new ArrayList<String>();
|
||||
for (String sortField : sortFields) {
|
||||
list.add(getSqlInjectSortField(sortField));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 orderBy type
|
||||
* 返回:字符串
|
||||
* <p>
|
||||
* 1.检测是否为 asc 或 desc 其中的一个
|
||||
* 2.限制sql注入
|
||||
*
|
||||
* @param orderType
|
||||
* @return
|
||||
*/
|
||||
public static String getSqlInjectOrderType(String orderType) {
|
||||
if (orderType == null) {
|
||||
return null;
|
||||
}
|
||||
orderType = orderType.trim();
|
||||
if (CommonConstant.ORDER_TYPE_ASC.equalsIgnoreCase(orderType)) {
|
||||
return CommonConstant.ORDER_TYPE_ASC;
|
||||
} else {
|
||||
return CommonConstant.ORDER_TYPE_DESC;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,12 +5,19 @@ 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.constant.TenantConstant;
|
||||
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;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jeecg.config.security.JeecgRedisOAuth2AuthorizationService;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @Author scott
|
||||
@ -27,12 +34,59 @@ public class TokenUtils {
|
||||
* @return
|
||||
*/
|
||||
public static String getTokenByRequest(HttpServletRequest request) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String token = request.getParameter("token");
|
||||
if (token == null) {
|
||||
token = request.getHeader("X-Access-Token");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 里传递的 token
|
||||
* @return
|
||||
*/
|
||||
public static String getTokenByRequest() {
|
||||
String token = null;
|
||||
try {
|
||||
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
|
||||
token = TokenUtils.getTokenByRequest(request);
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 里传递的 tenantId (租户ID)
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static String getTenantIdByRequest(HttpServletRequest request) {
|
||||
String tenantId = request.getParameter(TenantConstant.TENANT_ID);
|
||||
if (tenantId == null) {
|
||||
tenantId = oConvertUtils.getString(request.getHeader(CommonConstant.TENANT_ID));
|
||||
}
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 里传递的 lowAppId (低代码应用ID)
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static String getLowAppIdByRequest(HttpServletRequest request) {
|
||||
String lowAppId = request.getParameter(TenantConstant.FIELD_LOW_APP_ID);
|
||||
if (lowAppId == null) {
|
||||
lowAppId = oConvertUtils.getString(request.getHeader(TenantConstant.X_LOW_APP_ID));
|
||||
}
|
||||
return lowAppId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Token
|
||||
@ -58,8 +112,8 @@ public class TokenUtils {
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
LoginUser user = TokenUtils.getLoginUser(username, commonApi, redisUtil);
|
||||
//LoginUser user = commonApi.getUserByName(username);
|
||||
//LoginUser user = TokenUtils.getLoginUser(username, commonApi, redisUtil);
|
||||
LoginUser user = commonApi.getUserByName(username);
|
||||
if (user == null) {
|
||||
throw new JeecgBoot401Exception("用户不存在!");
|
||||
}
|
||||
@ -68,7 +122,7 @@ public class TokenUtils {
|
||||
throw new JeecgBoot401Exception("账号已被锁定,请联系管理员!");
|
||||
}
|
||||
// 校验token是否超时失效 & 或者账号密码是否错误
|
||||
if (!jwtTokenRefresh(token, username, user.getPassword(), redisUtil)) {
|
||||
if (!jwtTokenRefresh(token, username, user.getPassword())) {
|
||||
throw new JeecgBoot401Exception(CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||
}
|
||||
return true;
|
||||
@ -97,6 +151,15 @@ public class TokenUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean jwtTokenRefresh(String token, String userName, String passWord) {
|
||||
JeecgRedisOAuth2AuthorizationService authRedis = SpringContextUtils.getBean(JeecgRedisOAuth2AuthorizationService.class);
|
||||
OAuth2Authorization authorization = authRedis.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
if (Objects.nonNull(authorization) && JwtUtil.verify(token, userName, passWord)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录用户
|
||||
*
|
||||
@ -110,10 +173,11 @@ public class TokenUtils {
|
||||
//【重要】此处通过redis原生获取缓存用户,是为了解决微服务下system服务挂了,其他服务互调不通问题---
|
||||
if (redisUtil.hasKey(loginUserKey)) {
|
||||
try {
|
||||
loginUser = (LoginUser) redisUtil.get(loginUserKey);
|
||||
Object obj = redisUtil.get(loginUserKey);
|
||||
loginUser = (LoginUser) obj;
|
||||
//解密用户
|
||||
SensitiveInfoUtil.handlerObject(loginUser, false);
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -38,6 +38,11 @@ public class DynamicDBUtil {
|
||||
|
||||
String driverClassName = dbSource.getDbDriver();
|
||||
String url = dbSource.getDbUrl();
|
||||
// url配置成 “123” 会触发Druid死循环,一直去重复尝试连接
|
||||
if (oConvertUtils.isEmpty(url) || !url.toLowerCase().startsWith("jdbc:")) {
|
||||
throw new JeecgBootException("数据源URL配置格式不正确!");
|
||||
}
|
||||
|
||||
String dbUser = dbSource.getDbUsername();
|
||||
String dbPassword = dbSource.getDbPassword();
|
||||
dataSource.setDriverClassName(driverClassName);
|
||||
@ -47,6 +52,8 @@ public class DynamicDBUtil {
|
||||
dataSource.setTestOnBorrow(false);
|
||||
dataSource.setTestOnReturn(false);
|
||||
dataSource.setBreakAfterAcquireFailure(true);
|
||||
//设置超时时间60秒
|
||||
dataSource.setLoginTimeout(60);
|
||||
dataSource.setConnectionErrorRetryAttempts(0);
|
||||
dataSource.setUsername(dbUser);
|
||||
dataSource.setMaxWait(30000);
|
||||
|
||||
@ -34,11 +34,11 @@ public class FreemarkerParseFactory {
|
||||
/**
|
||||
* 文件缓存
|
||||
*/
|
||||
private static final Configuration TPL_CONFIG = new Configuration();
|
||||
private static final Configuration TPL_CONFIG = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
|
||||
/**
|
||||
* SQL 缓存
|
||||
*/
|
||||
private static final Configuration SQL_CONFIG = new Configuration();
|
||||
private static final Configuration SQL_CONFIG = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
|
||||
|
||||
private static StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
|
||||
|
||||
@ -47,8 +47,7 @@ public class FreemarkerParseFactory {
|
||||
.compile("(?ms)/\\*.*?\\*/|^\\s*//.*?$");
|
||||
|
||||
static {
|
||||
TPL_CONFIG.setClassForTemplateLoading(
|
||||
new FreemarkerParseFactory().getClass(), "/");
|
||||
TPL_CONFIG.setClassForTemplateLoading(new FreemarkerParseFactory().getClass(), "/");
|
||||
TPL_CONFIG.setNumberFormat("0.#####################");
|
||||
SQL_CONFIG.setTemplateLoader(stringTemplateLoader);
|
||||
SQL_CONFIG.setNumberFormat("0.#####################");
|
||||
@ -57,6 +56,7 @@ public class FreemarkerParseFactory {
|
||||
|
||||
//update-begin-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
|
||||
//https://ackcent.com/in-depth-freemarker-template-injection/
|
||||
TPL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
||||
SQL_CONFIG.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
||||
//update-end-author:taoyan date:2022-8-10 for: freemarker模板注入问题 禁止解析ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime。
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package org.jeecg.common.util.encryption;
|
||||
|
||||
import org.apache.shiro.codec.Base64;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @Description: AES 加密
|
||||
@ -49,7 +48,7 @@ public class AesEncryptUtil {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
|
||||
byte[] encrypted = cipher.doFinal(plaintext);
|
||||
|
||||
return Base64.encodeToString(encrypted);
|
||||
return Base64.getEncoder().encodeToString(encrypted);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@ -67,7 +66,7 @@ public class AesEncryptUtil {
|
||||
*/
|
||||
public static String desEncrypt(String data, String key, String iv) throws Exception {
|
||||
//update-begin-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
|
||||
byte[] encrypted1 = Base64.decode(data);
|
||||
byte[] encrypted1 = Base64.getDecoder().decode(data);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
|
||||
|
||||
@ -1,30 +1,75 @@
|
||||
package org.jeecg.common.util.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @Description: 校验文件敏感后缀
|
||||
* @author: lsq
|
||||
* @date: 2021年08月09日 15:29
|
||||
* @date: 2023年09月12日 15:29
|
||||
*/
|
||||
public class FileTypeFilter {
|
||||
|
||||
/**文件后缀*/
|
||||
private static String[] forbidType = {"jsp","php"};
|
||||
@Slf4j
|
||||
public class SsrfFileTypeFilter {
|
||||
|
||||
/**
|
||||
* 允许操作文件类型白名单
|
||||
*/
|
||||
private final static List<String> FILE_TYPE_WHITE_LIST = new ArrayList<>();
|
||||
/**初始化文件头类型,不够的自行补充*/
|
||||
final static HashMap<String, String> FILE_TYPE_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
//图片文件
|
||||
FILE_TYPE_WHITE_LIST.add("jpg");
|
||||
FILE_TYPE_WHITE_LIST.add("jpeg");
|
||||
FILE_TYPE_WHITE_LIST.add("png");
|
||||
FILE_TYPE_WHITE_LIST.add("gif");
|
||||
FILE_TYPE_WHITE_LIST.add("bmp");
|
||||
FILE_TYPE_WHITE_LIST.add("svg");
|
||||
FILE_TYPE_WHITE_LIST.add("ico");
|
||||
|
||||
//文本文件
|
||||
FILE_TYPE_WHITE_LIST.add("txt");
|
||||
FILE_TYPE_WHITE_LIST.add("doc");
|
||||
FILE_TYPE_WHITE_LIST.add("docx");
|
||||
FILE_TYPE_WHITE_LIST.add("pdf");
|
||||
FILE_TYPE_WHITE_LIST.add("csv");
|
||||
// FILE_TYPE_WHITE_LIST.add("xml");
|
||||
|
||||
//音视频文件
|
||||
FILE_TYPE_WHITE_LIST.add("mp4");
|
||||
FILE_TYPE_WHITE_LIST.add("avi");
|
||||
FILE_TYPE_WHITE_LIST.add("mov");
|
||||
FILE_TYPE_WHITE_LIST.add("wmv");
|
||||
FILE_TYPE_WHITE_LIST.add("mp3");
|
||||
FILE_TYPE_WHITE_LIST.add("wav");
|
||||
|
||||
//表格文件
|
||||
FILE_TYPE_WHITE_LIST.add("xls");
|
||||
FILE_TYPE_WHITE_LIST.add("xlsx");
|
||||
|
||||
//压缩文件
|
||||
FILE_TYPE_WHITE_LIST.add("zip");
|
||||
FILE_TYPE_WHITE_LIST.add("rar");
|
||||
FILE_TYPE_WHITE_LIST.add("7z");
|
||||
FILE_TYPE_WHITE_LIST.add("tar");
|
||||
|
||||
//app文件后缀
|
||||
FILE_TYPE_WHITE_LIST.add("apk");
|
||||
FILE_TYPE_WHITE_LIST.add("wgt");
|
||||
|
||||
//设置禁止文件的头部标记
|
||||
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");
|
||||
FILE_TYPE_MAP.put("3c3f7068700a0a2f2a2a0a202a205048", "php");
|
||||
FILE_TYPE_MAP.put("cafebabe0000002e0041", "class");
|
||||
FILE_TYPE_MAP.put("494e5345525420494e54", "sql");
|
||||
/* fileTypeMap.put("ffd8ffe000104a464946", "jpg");
|
||||
fileTypeMap.put("89504e470d0a1a0a0000", "png");
|
||||
fileTypeMap.put("47494638396126026f01", "gif");
|
||||
@ -89,17 +134,38 @@ public class FileTypeFilter {
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 文件类型过滤
|
||||
* 下载文件类型过滤
|
||||
*
|
||||
* @param filePath
|
||||
*/
|
||||
public static void checkDownloadFileType(String filePath) throws IOException {
|
||||
//文件后缀
|
||||
String suffix = getFileTypeBySuffix(filePath);
|
||||
log.info("suffix:{}", suffix);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new IOException("下载失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件类型过滤
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
public static void fileTypeFilter(MultipartFile file) throws Exception {
|
||||
public static void checkUploadFileType(MultipartFile file) throws Exception {
|
||||
//获取文件真是后缀
|
||||
String suffix = getFileType(file);
|
||||
for (String type : forbidType) {
|
||||
if (type.contains(suffix)) {
|
||||
throw new Exception("上传失败,非法文件类型:" + suffix);
|
||||
}
|
||||
|
||||
log.info("suffix:{}", suffix);
|
||||
boolean isAllowExtension = FILE_TYPE_WHITE_LIST.contains(suffix.toLowerCase());
|
||||
//是否允许下载的文件
|
||||
if (!isAllowExtension) {
|
||||
throw new Exception("上传失败,存在非法文件类型:" + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,8 +178,9 @@ public class FileTypeFilter {
|
||||
*/
|
||||
|
||||
private static String getFileType(MultipartFile file) throws Exception {
|
||||
//update-begin-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
||||
String fileExtendName = null;
|
||||
InputStream is;
|
||||
InputStream is = null;
|
||||
try {
|
||||
//is = new FileInputStream(file);
|
||||
is = file.getInputStream();
|
||||
@ -130,16 +197,29 @@ public class FileTypeFilter {
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.info("-----获取到的指定文件类型------"+fileExtendName);
|
||||
// 如果不是上述类型,则判断扩展名
|
||||
if (StringUtils.isBlank(fileExtendName)) {
|
||||
String fileName = file.getOriginalFilename();
|
||||
// 如果无扩展名,则直接返回空串
|
||||
if (-1 == fileName.indexOf(".")) {
|
||||
return "";
|
||||
}
|
||||
// 如果有扩展名,则返回扩展名
|
||||
return getFileTypeBySuffix(fileName);
|
||||
}
|
||||
log.info("-----最終的文件类型------"+fileExtendName);
|
||||
is.close();
|
||||
return fileExtendName;
|
||||
} catch (Exception exception) {
|
||||
throw new Exception(exception.getMessage(), exception);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return "";
|
||||
}finally {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
//update-end-author:liusq date:20230404 for: [issue/4672]方法造成的文件被占用,注释掉此方法tomcat就能自动清理掉临时文件
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1,22 +1,22 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.*;
|
||||
import java.sql.Date;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
@ -49,6 +49,27 @@ public class oConvertUtils {
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回decode解密字符串
|
||||
*
|
||||
* @param inStr
|
||||
* @return
|
||||
*/
|
||||
public static String decodeString(String inStr) {
|
||||
if (oConvertUtils.isEmpty(inStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
inStr = URLDecoder.decode(inStr, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
// 解决:URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "自动"
|
||||
//e.printStackTrace();
|
||||
}
|
||||
return inStr;
|
||||
}
|
||||
|
||||
public static String decode(String strIn, String sourceCode, String targetCode) {
|
||||
String temp = code2code(strIn, sourceCode, targetCode);
|
||||
return temp;
|
||||
@ -86,7 +107,7 @@ public class oConvertUtils {
|
||||
}
|
||||
|
||||
public static int getInt(String s, int defval) {
|
||||
if (s == null || s == "") {
|
||||
if (s == null || "".equals(s)) {
|
||||
return (defval);
|
||||
}
|
||||
try {
|
||||
@ -97,7 +118,7 @@ public class oConvertUtils {
|
||||
}
|
||||
|
||||
public static int getInt(String s) {
|
||||
if (s == null || s == "") {
|
||||
if (s == null || "".equals(s)) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
@ -108,7 +129,7 @@ public class oConvertUtils {
|
||||
}
|
||||
|
||||
public static int getInt(String s, Integer df) {
|
||||
if (s == null || s == "") {
|
||||
if (s == null || "".equals(s)) {
|
||||
return df;
|
||||
}
|
||||
try {
|
||||
@ -119,10 +140,10 @@ public class oConvertUtils {
|
||||
}
|
||||
|
||||
public static Integer[] getInts(String[] s) {
|
||||
Integer[] integer = new Integer[s.length];
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
Integer[] integer = new Integer[s.length];
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
integer[i] = Integer.parseInt(s[i]);
|
||||
}
|
||||
@ -131,7 +152,7 @@ public class oConvertUtils {
|
||||
}
|
||||
|
||||
public static double getDouble(String s, double defval) {
|
||||
if (s == null || s == "") {
|
||||
if (s == null || "".equals(s)) {
|
||||
return (defval);
|
||||
}
|
||||
try {
|
||||
@ -167,6 +188,17 @@ public class oConvertUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer getInteger(Object object, Integer defval) {
|
||||
if (isEmpty(object)) {
|
||||
return (defval);
|
||||
}
|
||||
try {
|
||||
return (Integer.parseInt(object.toString()));
|
||||
} catch (NumberFormatException e) {
|
||||
return (defval);
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer getInt(Object object) {
|
||||
if (isEmpty(object)) {
|
||||
return null;
|
||||
@ -226,6 +258,20 @@ public class oConvertUtils {
|
||||
return (String.valueOf(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回常规字符串(只保留字符串中的数字、字母、中文)
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
public static String getNormalString(String input) {
|
||||
if (oConvertUtils.isEmpty(input)) {
|
||||
return null;
|
||||
}
|
||||
String result = input.replaceAll("[^0-9a-zA-Z\\u4e00-\\u9fa5]", "");
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String getString(String s, String defval) {
|
||||
if (isEmpty(s)) {
|
||||
return (defval);
|
||||
@ -275,6 +321,22 @@ public class oConvertUtils {
|
||||
return (clazz.equals(String.class) || clazz.equals(Integer.class) || clazz.equals(Byte.class) || clazz.equals(Long.class) || clazz.equals(Double.class) || clazz.equals(Float.class) || clazz.equals(Character.class) || clazz.equals(Short.class) || clazz.equals(BigDecimal.class) || clazz.equals(BigInteger.class) || clazz.equals(Boolean.class) || clazz.equals(Date.class) || clazz.isPrimitive());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码base64
|
||||
*
|
||||
* @param base64Str base64字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeBase64Str(String base64Str) {
|
||||
byte[] byteContent = Base64.decodeBase64(base64Str);
|
||||
if (byteContent == null) {
|
||||
return null;
|
||||
}
|
||||
String decodedString = new String(byteContent);
|
||||
return decodedString;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param request
|
||||
* IP
|
||||
@ -353,23 +415,63 @@ public class oConvertUtils {
|
||||
/**
|
||||
* 判断元素是否在数组内
|
||||
*
|
||||
* @param substring
|
||||
* @param source
|
||||
* @param child
|
||||
* @param all
|
||||
* @return
|
||||
*/
|
||||
public static boolean isIn(String substring, String[] source) {
|
||||
if (source == null || source.length == 0) {
|
||||
public static boolean isIn(String child, String[] all) {
|
||||
if (all == null || all.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < source.length; i++) {
|
||||
String aSource = source[i];
|
||||
if (aSource.equals(substring)) {
|
||||
for (int i = 0; i < all.length; i++) {
|
||||
String aSource = all[i];
|
||||
if (aSource.equals(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断元素是否在数组内
|
||||
*
|
||||
* @param childArray
|
||||
* @param all
|
||||
* @return
|
||||
*/
|
||||
public static boolean isArrayIn(String[] childArray, String[] all) {
|
||||
if (all == null || all.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (String v : childArray) {
|
||||
if (!isIn(v, all)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断元素是否在数组内
|
||||
*
|
||||
* @param childArray
|
||||
* @param all
|
||||
* @return
|
||||
*/
|
||||
public static boolean isJsonArrayIn(JSONArray childArray, String[] all) {
|
||||
if (all == null || all.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] childs = (String[]) childArray.toArray();
|
||||
for (String v : childs) {
|
||||
if (!isIn(v, all)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Map对象
|
||||
*/
|
||||
@ -649,6 +751,148 @@ public class oConvertUtils {
|
||||
return (list == null || list.size() == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断旧值与新值 是否相等
|
||||
*
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
* @return
|
||||
*/
|
||||
public static boolean isEqual(Object oldVal, Object newVal) {
|
||||
if (oldVal != null && newVal != null) {
|
||||
if (isArray(oldVal)) {
|
||||
return equalityOfArrays((Object[]) oldVal, (Object[]) newVal);
|
||||
}else if(oldVal instanceof JSONArray){
|
||||
if(newVal instanceof JSONArray){
|
||||
return equalityOfJSONArray((JSONArray) oldVal, (JSONArray) newVal);
|
||||
}else{
|
||||
if (isEmpty(newVal) && (oldVal == null || ((JSONArray) oldVal).size() == 0)) {
|
||||
return true;
|
||||
}
|
||||
List<Object> arrayStr = Arrays.asList(newVal.toString().split(","));
|
||||
JSONArray newValArray = new JSONArray(arrayStr);
|
||||
return equalityOfJSONArray((JSONArray) oldVal, newValArray);
|
||||
}
|
||||
}else{
|
||||
return oldVal.equals(newVal);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (oldVal == null && newVal == null) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法描述 判断一个对象是否是一个数组
|
||||
*
|
||||
* @param obj
|
||||
* @return
|
||||
* @author yaomy
|
||||
* @date 2018年2月5日 下午5:03:00
|
||||
*/
|
||||
public static boolean isArray(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
return obj.getClass().isArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取集合的大小
|
||||
*
|
||||
* @param collection
|
||||
* @return
|
||||
*/
|
||||
public static int getCollectionSize(Collection<?> collection) {
|
||||
return collection != null ? collection.size() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个数组是否相等(数组元素不分顺序)
|
||||
*
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
* @return
|
||||
*/
|
||||
public static boolean equalityOfJSONArray(JSONArray oldVal, JSONArray newVal) {
|
||||
if (oldVal != null && newVal != null) {
|
||||
Object[] oldValArray = oldVal.toArray();
|
||||
Object[] newValArray = newVal.toArray();
|
||||
return equalityOfArrays(oldValArray,newValArray);
|
||||
} else {
|
||||
if ((oldVal == null || oldVal.size() == 0) && (newVal == null || newVal.size() == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较带逗号的字符串
|
||||
* QQYUN-5212【简流】按日期触发 多选 人员组件 选择顺序不一致时 不触发,应该是统一问题 包括多选部门组件
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
* @return
|
||||
*/
|
||||
public static boolean equalityOfStringArrays(String oldVal, String newVal) {
|
||||
if(oldVal.equals(newVal)){
|
||||
return true;
|
||||
}
|
||||
if(oldVal.indexOf(",")>=0 && newVal.indexOf(",")>=0){
|
||||
String[] arr1 = oldVal.split(",");
|
||||
String[] arr2 = newVal.split(",");
|
||||
if(arr1.length == arr2.length){
|
||||
boolean flag = true;
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
for(String s1: arr1){
|
||||
map.put(s1, 1);
|
||||
}
|
||||
for(String s2: arr2){
|
||||
if(map.get(s2) == null){
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个数组是否相等(数组元素不分顺序)
|
||||
*
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
* @return
|
||||
*/
|
||||
public static boolean equalityOfArrays(Object[] oldVal, Object newVal[]) {
|
||||
if (oldVal != null && newVal != null) {
|
||||
Arrays.sort(oldVal);
|
||||
Arrays.sort(newVal);
|
||||
return Arrays.equals(oldVal, newVal);
|
||||
} else {
|
||||
if ((oldVal == null || oldVal.length == 0) && (newVal == null || newVal.length == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// public static void main(String[] args) {
|
||||
//// String[] a = new String[]{"1", "2"};
|
||||
//// String[] b = new String[]{"2", "1"};
|
||||
// Integer a = null;
|
||||
// Integer b = 1;
|
||||
// System.out.println(oConvertUtils.isEqual(a, b));
|
||||
// }
|
||||
|
||||
/**
|
||||
* 判断 list 是否不为空
|
||||
*
|
||||
@ -677,4 +921,112 @@ public class oConvertUtils {
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将List 转成 JSONArray
|
||||
* @return
|
||||
*/
|
||||
public static JSONArray list2JSONArray(List<String> list){
|
||||
if(list==null || list.size()==0){
|
||||
return null;
|
||||
}
|
||||
JSONArray array = new JSONArray();
|
||||
for(String str: list){
|
||||
array.add(str);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个list中的元素是否完全一致
|
||||
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
|
||||
* @return
|
||||
*/
|
||||
public static boolean isEqList(List<String> list1, List<String> list2){
|
||||
if(list1.size() != list2.size()){
|
||||
return false;
|
||||
}
|
||||
for(String str1: list1){
|
||||
boolean flag = false;
|
||||
for(String str2: list2){
|
||||
if(str1.equals(str2)){
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!flag){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断 list1中的元素是否在list2中出现
|
||||
* QQYUN-5326【简流】获取组织人员 单/多 筛选条件 没有部门筛选
|
||||
* @param list1
|
||||
* @param list2
|
||||
* @return
|
||||
*/
|
||||
public static boolean isInList(List<String> list1, List<String> list2){
|
||||
for(String str1: list1){
|
||||
boolean flag = false;
|
||||
for(String str2: list2){
|
||||
if(str1.equals(str2)){
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(flag){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件大小转成MB
|
||||
* @param uploadCount
|
||||
* @return
|
||||
*/
|
||||
public static Double calculateFileSizeToMb(Long uploadCount){
|
||||
double count = 0.0;
|
||||
if(uploadCount>0) {
|
||||
BigDecimal bigDecimal = new BigDecimal(uploadCount);
|
||||
//换算成MB
|
||||
BigDecimal divide = bigDecimal.divide(new BigDecimal(1048576));
|
||||
count = divide.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
|
||||
return count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* map转str
|
||||
*
|
||||
* @param map
|
||||
* @return
|
||||
*/
|
||||
public static String mapToString(Map<String, String[]> map) {
|
||||
if (map == null || map.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String, String[]> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String[] values = entry.getValue();
|
||||
sb.append(key).append("=");
|
||||
sb.append(values != null ? StringUtils.join(values, ",") : "");
|
||||
sb.append("&");
|
||||
}
|
||||
|
||||
String result = sb.toString();
|
||||
if (result.endsWith("&")) {
|
||||
result = result.substring(0, sb.length() - 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,11 +7,11 @@ import com.aliyun.oss.model.CannedAccessControlList;
|
||||
import com.aliyun.oss.model.OSSObject;
|
||||
import com.aliyun.oss.model.PutObjectResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.tomcat.util.http.fileupload.FileItemStream;
|
||||
import org.apache.commons.fileupload.FileItemStream;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.util.CommonUtils;
|
||||
import org.jeecg.common.util.filter.FileTypeFilter;
|
||||
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
|
||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@ -98,7 +98,7 @@ public class OssBootUtil {
|
||||
*/
|
||||
public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception {
|
||||
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
FileTypeFilter.fileTypeFilter(file);
|
||||
SsrfFileTypeFilter.checkUploadFileType(file);
|
||||
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
|
||||
|
||||
String filePath = null;
|
||||
@ -142,10 +142,10 @@ public class OssBootUtil {
|
||||
log.info("------OSS文件上传成功------" + fileUrl);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.getMessage(),e);
|
||||
return null;
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.getMessage(),e);
|
||||
return null;
|
||||
}
|
||||
return filePath;
|
||||
@ -299,10 +299,13 @@ public class OssBootUtil {
|
||||
//update-end---author:liusq Date:20220120 for:替换objectName前缀,防止key不一致导致获取不到文件----
|
||||
if(ossClient.doesObjectExist(bucketName,objectName)){
|
||||
URL url = ossClient.generatePresignedUrl(bucketName,objectName,expires);
|
||||
return URLDecoder.decode(url.toString(),"UTF-8");
|
||||
//log.info("原始url : {}", url.toString());
|
||||
//log.info("decode url : {}", URLDecoder.decode(url.toString(), "UTF-8"));
|
||||
//【issues/4023】问题 oss外链经过转编码后,部分无效,大概在三分一;无需转编码直接返回即可 #4023
|
||||
return url.toString();
|
||||
}
|
||||
}catch (Exception e){
|
||||
log.info("文件路径获取失败" + e.getMessage());
|
||||
log.info("文件路径获取失败" + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
package org.jeecg.common.util.security;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 查询表/字段 黑名单处理
|
||||
@ -21,6 +25,11 @@ public abstract class AbstractQueryBlackListHandler {
|
||||
*/
|
||||
public static Map<String, String> ruleMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 以下字符不能出现在表名中或是字段名中
|
||||
*/
|
||||
public static final Pattern ILLEGAL_NAME_REG = Pattern.compile("[-]{2,}");
|
||||
|
||||
static {
|
||||
ruleMap.put("sys_user", "password,salt");
|
||||
}
|
||||
@ -41,31 +50,87 @@ public abstract class AbstractQueryBlackListHandler {
|
||||
* @return
|
||||
*/
|
||||
public boolean isPass(String sql) {
|
||||
List<QueryTable> list = this.getQueryTableInfo(sql.toLowerCase());
|
||||
List<QueryTable> list = null;
|
||||
//【jeecg-boot/issues/4040】在线报表不支持子查询,解析报错 #4040
|
||||
try {
|
||||
list = this.getQueryTableInfo(sql.toLowerCase());
|
||||
} catch (Exception e) {
|
||||
log.warn("校验sql语句,解析报错:{}",e.getMessage());
|
||||
}
|
||||
|
||||
if(list==null){
|
||||
return true;
|
||||
}
|
||||
log.info("--获取sql信息--", list.toString());
|
||||
boolean flag = true;
|
||||
log.info(" 获取sql信息 :{} ", list.toString());
|
||||
boolean flag = checkTableAndFieldsName(list);
|
||||
if(flag == false){
|
||||
return false;
|
||||
}
|
||||
for (QueryTable table : list) {
|
||||
String name = table.getName();
|
||||
String fieldString = ruleMap.get(name);
|
||||
String fieldRule = ruleMap.get(name);
|
||||
// 有没有配置这张表
|
||||
if (fieldString != null) {
|
||||
if ("*".equals(fieldString) || table.isAll()) {
|
||||
if (fieldRule != null) {
|
||||
if ("*".equals(fieldRule) || table.isAll()) {
|
||||
flag = false;
|
||||
log.warn("sql黑名单校验,表【"+name+"】禁止查询");
|
||||
break;
|
||||
} else if (table.existSameField(fieldString)) {
|
||||
} else if (table.existSameField(fieldRule)) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 返回黑名单校验结果(不合法直接抛出异常)
|
||||
if(!flag){
|
||||
log.error(this.getError());
|
||||
throw new JeecgSqlInjectionException(this.getError());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验表名和字段名是否有效,或是是否会带些特殊的字符串进行sql注入
|
||||
* issues/4983 SQL Injection in 3.5.1 #4983
|
||||
* @return
|
||||
*/
|
||||
private boolean checkTableAndFieldsName(List<QueryTable> list){
|
||||
boolean flag = true;
|
||||
for(QueryTable queryTable: list){
|
||||
String tableName = queryTable.getName();
|
||||
if(hasSpecialString(tableName)){
|
||||
flag = false;
|
||||
log.warn("sql黑名单校验,表名【"+tableName+"】包含特殊字符");
|
||||
break;
|
||||
}
|
||||
Set<String> fields = queryTable.getFields();
|
||||
for(String name: fields){
|
||||
if(hasSpecialString(name)){
|
||||
flag = false;
|
||||
log.warn("sql黑名单校验,字段名【"+name+"】包含特殊字符");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否包含特殊的字符串
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
private boolean hasSpecialString(String name){
|
||||
Matcher m = ILLEGAL_NAME_REG.matcher(name);
|
||||
if (m.find()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询的表的信息
|
||||
*/
|
||||
@ -132,21 +197,21 @@ public abstract class AbstractQueryBlackListHandler {
|
||||
* @return
|
||||
*/
|
||||
public boolean existSameField(String fieldString) {
|
||||
String[] arr = fieldString.split(",");
|
||||
for (String exp : fields) {
|
||||
for (String config : arr) {
|
||||
if (exp.equals(config)) {
|
||||
String[] controlFields = fieldString.split(",");
|
||||
for (String sqlField : fields) {
|
||||
for (String controlField : controlFields) {
|
||||
if (sqlField.equals(controlField)) {
|
||||
// 非常明确的列直接比较
|
||||
log.warn("sql黑名单校验,表【"+name+"】中字段【"+config+"】禁止查询");
|
||||
log.warn("sql黑名单校验,表【"+name+"】中字段【"+controlField+"】禁止查询");
|
||||
return true;
|
||||
} else {
|
||||
// 使用表达式的列 只能判读字符串包含了
|
||||
String aliasColumn = config;
|
||||
if (alias != null && alias.length() > 0) {
|
||||
aliasColumn = alias + "." + config;
|
||||
String aliasColumn = controlField;
|
||||
if (StringUtils.isNotBlank(alias)) {
|
||||
aliasColumn = alias + "." + controlField;
|
||||
}
|
||||
if (exp.indexOf(aliasColumn) > 0) {
|
||||
log.warn("sql黑名单校验,表【"+name+"】中字段【"+config+"】禁止查询");
|
||||
if (sqlField.indexOf(aliasColumn) != -1) {
|
||||
log.warn("sql黑名单校验,表【"+name+"】中字段【"+controlField+"】禁止查询");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package org.jeecg.common.util.sqlInjection;
|
||||
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserDefaultVisitor;
|
||||
import net.sf.jsqlparser.parser.SimpleNode;
|
||||
import net.sf.jsqlparser.statement.select.UnionOp;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
|
||||
/**
|
||||
* 基于抽象语法树(AST)的注入攻击分析实现
|
||||
*
|
||||
* @author guyadong
|
||||
*/
|
||||
public class InjectionAstNodeVisitor extends CCJSqlParserDefaultVisitor {
|
||||
public InjectionAstNodeVisitor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理禁止联合查询
|
||||
*
|
||||
* @param node
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Object visit(SimpleNode node, Object data) {
|
||||
Object value = node.jjtGetValue();
|
||||
if (value instanceof UnionOp) {
|
||||
throw new JeecgSqlInjectionException("DISABLE UNION");
|
||||
}
|
||||
return super.visit(node, data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,172 @@
|
||||
package org.jeecg.common.util.sqlInjection;
|
||||
|
||||
|
||||
import net.sf.jsqlparser.expression.BinaryExpression;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.Function;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ComparisonOperator;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.statement.select.Join;
|
||||
import net.sf.jsqlparser.statement.select.OrderByElement;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.SelectItem;
|
||||
import net.sf.jsqlparser.statement.select.SubSelect;
|
||||
import net.sf.jsqlparser.statement.select.WithItem;
|
||||
import net.sf.jsqlparser.util.TablesNamesFinder;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
import org.jeecg.common.util.sqlInjection.parse.ConstAnalyzer;
|
||||
import org.jeecg.common.util.sqlInjection.parse.ParserSupport;
|
||||
|
||||
/**
|
||||
* 基于SQL语法对象的SQL注入攻击分析实现
|
||||
*
|
||||
* @author guyadong
|
||||
*/
|
||||
public class InjectionSyntaxObjectAnalyzer extends TablesNamesFinder {
|
||||
/**
|
||||
* 危险函数名
|
||||
*/
|
||||
private static final String DANGROUS_FUNCTIONS = "(sleep|benchmark|extractvalue|updatexml|ST_LatFromGeoHash|ST_LongFromGeoHash|GTID_SUBSET|GTID_SUBTRACT|floor|ST_Pointfromgeohash"
|
||||
+ "|geometrycollection|multipoint|polygon|multipolygon|linestring|multilinestring)";
|
||||
|
||||
private static ThreadLocal<Boolean> disableSubselect = new ThreadLocal<Boolean>() {
|
||||
@Override
|
||||
protected Boolean initialValue() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
private ConstAnalyzer constAnalyzer = new ConstAnalyzer();
|
||||
|
||||
public InjectionSyntaxObjectAnalyzer() {
|
||||
super();
|
||||
init(true);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitBinaryExpression(BinaryExpression binaryExpression) {
|
||||
if (binaryExpression instanceof ComparisonOperator) {
|
||||
if (isConst(binaryExpression.getLeftExpression()) && isConst(binaryExpression.getRightExpression())) {
|
||||
/** 禁用恒等式 */
|
||||
throw new JeecgSqlInjectionException("DISABLE IDENTICAL EQUATION " + binaryExpression);
|
||||
}
|
||||
}
|
||||
super.visitBinaryExpression(binaryExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AndExpression andExpression) {
|
||||
super.visit(andExpression);
|
||||
checkConstExpress(andExpression.getLeftExpression());
|
||||
checkConstExpress(andExpression.getRightExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(OrExpression orExpression) {
|
||||
super.visit(orExpression);
|
||||
checkConstExpress(orExpression.getLeftExpression());
|
||||
checkConstExpress(orExpression.getRightExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Function function) {
|
||||
if (function.getName().matches(DANGROUS_FUNCTIONS)) {
|
||||
/** 禁用危险函数 */
|
||||
throw new JeecgSqlInjectionException("DANGROUS FUNCTION: " + function.getName());
|
||||
}
|
||||
super.visit(function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(WithItem withItem) {
|
||||
try {
|
||||
/** 允许 WITH 语句中的子查询 */
|
||||
disableSubselect.set(false);
|
||||
super.visit(withItem);
|
||||
} finally {
|
||||
disableSubselect.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SubSelect subSelect) {
|
||||
try {
|
||||
/** 允许语句中的子查询 */
|
||||
disableSubselect.set(false);
|
||||
super.visit(subSelect);
|
||||
} finally {
|
||||
disableSubselect.set(true);
|
||||
}
|
||||
// if (disableSubselect.get()) {
|
||||
// // 禁用子查询
|
||||
// throw new JeecgSqlInjectionException("DISABLE subselect " + subSelect);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Column tableColumn) {
|
||||
if (ParserSupport.isBoolean(tableColumn)) {
|
||||
throw new JeecgSqlInjectionException("DISABLE CONST BOOL " + tableColumn);
|
||||
}
|
||||
super.visit(tableColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(PlainSelect plainSelect) {
|
||||
if (plainSelect.getSelectItems() != null) {
|
||||
for (SelectItem item : plainSelect.getSelectItems()) {
|
||||
item.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (plainSelect.getFromItem() != null) {
|
||||
plainSelect.getFromItem().accept(this);
|
||||
}
|
||||
|
||||
if (plainSelect.getJoins() != null) {
|
||||
for (Join join : plainSelect.getJoins()) {
|
||||
join.getRightItem().accept(this);
|
||||
for (Expression e : join.getOnExpressions()) {
|
||||
e.accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (plainSelect.getWhere() != null) {
|
||||
plainSelect.getWhere().accept(this);
|
||||
checkConstExpress(plainSelect.getWhere());
|
||||
}
|
||||
|
||||
if (plainSelect.getHaving() != null) {
|
||||
plainSelect.getHaving().accept(this);
|
||||
}
|
||||
|
||||
if (plainSelect.getOracleHierarchical() != null) {
|
||||
plainSelect.getOracleHierarchical().accept(this);
|
||||
}
|
||||
if (plainSelect.getOrderByElements() != null) {
|
||||
for (OrderByElement orderByElement : plainSelect.getOrderByElements()) {
|
||||
orderByElement.getExpression().accept(this);
|
||||
}
|
||||
}
|
||||
if (plainSelect.getGroupBy() != null) {
|
||||
for (Expression expression : plainSelect.getGroupBy().getGroupByExpressionList().getExpressions()) {
|
||||
expression.accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isConst(Expression expression) {
|
||||
return constAnalyzer.isConstExpression(expression);
|
||||
}
|
||||
|
||||
private void checkConstExpress(Expression expression) {
|
||||
if (constAnalyzer.isConstExpression(expression)) {
|
||||
/** 禁用常量表达式 */
|
||||
throw new JeecgSqlInjectionException("DISABLE CONST EXPRESSION " + expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
package org.jeecg.common.util.sqlInjection;
|
||||
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
import org.jeecg.common.util.sqlInjection.parse.ParserSupport;
|
||||
;
|
||||
|
||||
/**
|
||||
* SQL注入攻击分析器
|
||||
*
|
||||
* @author guyadong
|
||||
* 参考:
|
||||
* https://blog.csdn.net/10km/article/details/127767358
|
||||
* https://gitee.com/l0km/sql2java/tree/dev/sql2java-manager/src/main/java/gu/sql2java/parser
|
||||
*/
|
||||
public class SqlInjectionAnalyzer {
|
||||
|
||||
//启用/关闭注入攻击检查
|
||||
private boolean injectCheckEnable = true;
|
||||
//防止SQL注入攻击分析实现
|
||||
private final InjectionSyntaxObjectAnalyzer injectionChecker;
|
||||
private final InjectionAstNodeVisitor injectionVisitor;
|
||||
|
||||
public SqlInjectionAnalyzer() {
|
||||
this.injectionChecker = new InjectionSyntaxObjectAnalyzer();
|
||||
this.injectionVisitor = new InjectionAstNodeVisitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/关闭注入攻击检查,默认启动
|
||||
*
|
||||
* @param enable
|
||||
* @return
|
||||
*/
|
||||
public SqlInjectionAnalyzer injectCheckEnable(boolean enable) {
|
||||
injectCheckEnable = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对解析后的SQL对象执行注入攻击分析,有注入攻击的危险则抛出异常{@link JeecgSqlInjectionException}
|
||||
*
|
||||
* @param sqlParserInfo
|
||||
* @throws JeecgSqlInjectionException
|
||||
*/
|
||||
public ParserSupport.SqlParserInfo injectAnalyse(ParserSupport.SqlParserInfo sqlParserInfo) throws JeecgSqlInjectionException {
|
||||
if (null != sqlParserInfo && injectCheckEnable) {
|
||||
/** SQL注入攻击检查 */
|
||||
sqlParserInfo.statement.accept(injectionChecker);
|
||||
sqlParserInfo.simpleNode.jjtAccept(injectionVisitor, null);
|
||||
}
|
||||
return sqlParserInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* sql校验
|
||||
*/
|
||||
public static void checkSql(String sql,boolean check){
|
||||
SqlInjectionAnalyzer sqlInjectionAnalyzer = new SqlInjectionAnalyzer();
|
||||
sqlInjectionAnalyzer.injectCheckEnable(check);
|
||||
ParserSupport.SqlParserInfo sqlParserInfo = ParserSupport.parse0(sql, null,null);
|
||||
sqlInjectionAnalyzer.injectAnalyse(sqlParserInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,569 @@
|
||||
package org.jeecg.common.util.sqlInjection.parse;
|
||||
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseOr;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseRightShift;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseXor;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.Concat;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.Division;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.IntegerDivision;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.Modulo;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication;
|
||||
import net.sf.jsqlparser.expression.operators.arithmetic.Subtraction;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.XorExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.Between;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.FullTextSearch;
|
||||
import net.sf.jsqlparser.expression.operators.relational.GeometryDistance;
|
||||
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
|
||||
import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ItemsListVisitor;
|
||||
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
|
||||
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.Matches;
|
||||
import net.sf.jsqlparser.expression.operators.relational.MinorThan;
|
||||
import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;
|
||||
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.NamedExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.RegExpMatchOperator;
|
||||
import net.sf.jsqlparser.expression.operators.relational.RegExpMySQLOperator;
|
||||
import net.sf.jsqlparser.expression.operators.relational.SimilarToExpression;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.statement.select.AllColumns;
|
||||
import net.sf.jsqlparser.statement.select.AllTableColumns;
|
||||
import net.sf.jsqlparser.statement.select.OrderByElement;
|
||||
import net.sf.jsqlparser.statement.select.SubSelect;
|
||||
|
||||
/**
|
||||
* 判断表达是否为常量的分析器
|
||||
*
|
||||
* @author guyadong
|
||||
*/
|
||||
public class ConstAnalyzer implements ExpressionVisitor, ItemsListVisitor {
|
||||
|
||||
private static ThreadLocal<Boolean> constFlag = new ThreadLocal<Boolean>() {
|
||||
@Override
|
||||
protected Boolean initialValue() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void visit(NullValue value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Function function) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SignedExpression expr) {
|
||||
expr.getExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(JdbcParameter parameter) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(JdbcNamedParameter parameter) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(DoubleValue value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(LongValue value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(DateValue value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(TimeValue value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(TimestampValue value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Parenthesis parenthesis) {
|
||||
parenthesis.getExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(StringValue value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Addition expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Division expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(IntegerDivision expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Multiplication expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Subtraction expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AndExpression expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(OrExpression expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(XorExpression expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Between expr) {
|
||||
expr.getLeftExpression().accept(this);
|
||||
expr.getBetweenExpressionStart().accept(this);
|
||||
expr.getBetweenExpressionEnd().accept(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于处理 OverlapsCondition 类型的表达式
|
||||
* @param overlapsCondition
|
||||
*/
|
||||
@Override
|
||||
public void visit(OverlapsCondition overlapsCondition) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
/**
|
||||
* 用于处理 SafeCastExpression 类型的表达式。
|
||||
* @param safeCastExpression
|
||||
*/
|
||||
@Override
|
||||
public void visit(SafeCastExpression safeCastExpression) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(EqualsTo expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(GreaterThan expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(GreaterThanEquals expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(InExpression expr) {
|
||||
if (expr.getLeftExpression() != null) {
|
||||
expr.getLeftExpression().accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(IsNullExpression expr) {
|
||||
expr.getLeftExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(FullTextSearch expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(IsBooleanExpression expr) {
|
||||
expr.getLeftExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(LikeExpression expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MinorThan expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MinorThanEquals expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(NotEqualsTo expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Column column) {
|
||||
if (!ParserSupport.isBoolean(column)) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SubSelect subSelect) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(CaseExpression expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(WhenClause expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ExistsExpression expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AnyComparisonExpression expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Concat expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Matches expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(BitwiseAnd expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(BitwiseOr expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(BitwiseXor expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(CastExpression expr) {
|
||||
expr.getLeftExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(TryCastExpression expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Modulo expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AnalyticExpression expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ExtractExpression expr) {
|
||||
expr.getExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(IntervalExpression expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(OracleHierarchicalExpression expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(RegExpMatchOperator expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ExpressionList expressionList) {
|
||||
for (Expression expr : expressionList.getExpressions()) {
|
||||
expr.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(NamedExpressionList namedExpressionList) {
|
||||
for (Expression expr : namedExpressionList.getExpressions()) {
|
||||
expr.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MultiExpressionList multiExprList) {
|
||||
for (ExpressionList list : multiExprList.getExpressionLists()) {
|
||||
visit(list);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(NotExpression notExpr) {
|
||||
notExpr.getExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(BitwiseRightShift expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(BitwiseLeftShift expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
protected void visitBinaryExpression(BinaryExpression expr) {
|
||||
expr.getLeftExpression().accept(this);
|
||||
expr.getRightExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(JsonExpression jsonExpr) {
|
||||
jsonExpr.getExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(JsonOperator expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(RegExpMySQLOperator expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(UserVariable var) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(NumericBind bind) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(KeepExpression expr) {
|
||||
for (OrderByElement element : expr.getOrderByElements()) {
|
||||
element.getExpression().accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MySQLGroupConcat groupConcat) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ValueListExpression valueListExpression) {
|
||||
for (Expression expr : valueListExpression.getExpressionList().getExpressions()) {
|
||||
expr.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AllColumns allColumns) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AllTableColumns allTableColumns) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(AllValue allValue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(IsDistinctExpression isDistinctExpression) {
|
||||
visitBinaryExpression(isDistinctExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(RowGetExpression rowGetExpression) {
|
||||
rowGetExpression.getExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(HexValue hexValue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(OracleHint hint) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(TimeKeyExpression timeKeyExpression) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(DateTimeLiteralExpression literal) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(NextValExpression nextVal) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(CollateExpression col) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SimilarToExpression expr) {
|
||||
visitBinaryExpression(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ArrayExpression array) {
|
||||
array.getObjExpression().accept(this);
|
||||
if (array.getIndexExpression() != null) {
|
||||
array.getIndexExpression().accept(this);
|
||||
}
|
||||
if (array.getStartIndexExpression() != null) {
|
||||
array.getStartIndexExpression().accept(this);
|
||||
}
|
||||
if (array.getStopIndexExpression() != null) {
|
||||
array.getStopIndexExpression().accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ArrayConstructor aThis) {
|
||||
for (Expression expression : aThis.getExpressions()) {
|
||||
expression.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(VariableAssignment var) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(XMLSerializeExpr expr) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(TimezoneExpression expr) {
|
||||
expr.getLeftExpression().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(JsonAggregateFunction expression) {
|
||||
Expression expr = expression.getExpression();
|
||||
if (expr != null) {
|
||||
expr.accept(this);
|
||||
}
|
||||
|
||||
expr = expression.getFilterExpression();
|
||||
if (expr != null) {
|
||||
expr.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(JsonFunction expression) {
|
||||
for (JsonFunctionExpression expr : expression.getExpressions()) {
|
||||
expr.getExpression().accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ConnectByRootOperator connectByRootOperator) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(GeometryDistance geometryDistance) {
|
||||
visitBinaryExpression(geometryDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(RowConstructor rowConstructor) {
|
||||
constFlag.set(false);
|
||||
}
|
||||
|
||||
public boolean isConstExpression(Expression expression) {
|
||||
if (null != expression) {
|
||||
constFlag.set(true);
|
||||
expression.accept(this);
|
||||
return constFlag.get();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,177 @@
|
||||
package org.jeecg.common.util.sqlInjection.parse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.parser.*;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.Select;
|
||||
import net.sf.jsqlparser.statement.select.SelectBody;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
|
||||
/**
|
||||
* 解析sql支持
|
||||
*/
|
||||
@Slf4j
|
||||
public class ParserSupport {
|
||||
/**
|
||||
* 解析SELECT SQL语句,解析失败或非SELECT语句则抛出异常
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
public static Select parseSelect(String sql) {
|
||||
Statement stmt;
|
||||
try {
|
||||
stmt = CCJSqlParserUtil.parse(checkNotNull(sql, "sql is null"));
|
||||
} catch (JSQLParserException e) {
|
||||
throw new JeecgBootException(e);
|
||||
}
|
||||
checkArgument(stmt instanceof Select, "%s is not SELECT statment", sql);
|
||||
Select select = (Select) stmt;
|
||||
SelectBody selectBody = select.getSelectBody();
|
||||
// 暂时只支持简单的SELECT xxxx FROM ....语句不支持复杂语句如WITH
|
||||
checkArgument(selectBody instanceof PlainSelect, "ONLY SUPPORT plain select statement %s", sql);
|
||||
return (Select) stmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析SELECT SQL语句,解析失败或非SELECT语句则
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
public static Select parseSelectUnchecked(String sql) {
|
||||
try {
|
||||
return parseSelect(sql);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现SQL语句解析,解析成功则返回解析后的{@link Statement},
|
||||
* 并通过{@code visitor}参数提供基于AST(抽象语法树)的遍历所有节点的能力。
|
||||
*
|
||||
* @param sql SQL语句
|
||||
* @param visitor 遍历所有节点的{@link SimpleNodeVisitor}接口实例,为{@code null}忽略
|
||||
* @param sqlSyntaxNormalizer SQL语句分析转换器,为{@code null}忽略
|
||||
* @throws JSQLParserException 输入的SQL语句有语法错误
|
||||
* @see #parse0(String, CCJSqlParserVisitor, SqlSyntaxNormalizer)
|
||||
*/
|
||||
public static Statement parse(String sql, CCJSqlParserVisitor visitor, SqlSyntaxNormalizer sqlSyntaxNormalizer) throws JSQLParserException {
|
||||
return parse0(sql, visitor, sqlSyntaxNormalizer).statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参照{@link CCJSqlParserUtil#parseAST(String)}和{@link CCJSqlParserUtil#parse(String)}实现SQL语句解析,
|
||||
* 解析成功则返回解析后的{@link SqlParserInfo}对象,
|
||||
* 并通过{@code visitor}参数提供基于AST(抽象语法树)的遍历所有节点的能力。
|
||||
*
|
||||
* @param sql SQL语句
|
||||
* @param visitor 遍历所有节点的{@link SimpleNodeVisitor}接口实例,为{@code null}忽略
|
||||
* @param sqlSyntaxAnalyzer SQL语句分析转换器,为{@code null}忽略
|
||||
* @throws JSQLParserException 输入的SQL语句有语法错误
|
||||
* @see net.sf.jsqlparser.parser.Node#jjtAccept(SimpleNodeVisitor, Object)
|
||||
*/
|
||||
public static SqlParserInfo parse0(String sql, CCJSqlParserVisitor visitor, SqlSyntaxNormalizer sqlSyntaxAnalyzer) throws JeecgSqlInjectionException {
|
||||
|
||||
//检查是否非select开头,暂不支持
|
||||
if(!sql.toLowerCase().trim().startsWith("select ")) {
|
||||
log.warn("传入sql 非select开头,不支持非select开头的语句解析!");
|
||||
return null;
|
||||
}
|
||||
|
||||
//检查是否存储过程,暂不支持
|
||||
if(sql.toLowerCase().trim().startsWith("call ")){
|
||||
log.warn("传入call 开头存储过程,不支持存储过程解析!");
|
||||
return null;
|
||||
}
|
||||
|
||||
//检查特殊语义的特殊字符,目前检查冒号、$、#三种特殊语义字符
|
||||
String specialCharacters = "[:$#]";
|
||||
Pattern pattern = Pattern.compile(specialCharacters);
|
||||
Matcher matcher = pattern.matcher(sql);
|
||||
if (matcher.find()) {
|
||||
sql = sql.replaceAll("[:$#]", "@");
|
||||
}
|
||||
|
||||
checkArgument(null != sql, "sql is null");
|
||||
boolean allowComplexParsing = CCJSqlParserUtil.getNestingDepth(sql) <= CCJSqlParserUtil.ALLOWED_NESTING_DEPTH;
|
||||
|
||||
CCJSqlParser parser = CCJSqlParserUtil.newParser(sql).withAllowComplexParsing(allowComplexParsing);
|
||||
Statement stmt;
|
||||
try {
|
||||
stmt = parser.Statement();
|
||||
} catch (Exception ex) {
|
||||
log.error("请注意,SQL语法可能存在问题---> {}", ex.getMessage());
|
||||
throw new JeecgSqlInjectionException("请注意,SQL语法可能存在问题:"+sql);
|
||||
}
|
||||
if (null != visitor) {
|
||||
parser.getASTRoot().jjtAccept(visitor, null);
|
||||
}
|
||||
if (null != sqlSyntaxAnalyzer) {
|
||||
stmt.accept(sqlSyntaxAnalyzer.resetChanged());
|
||||
}
|
||||
return new SqlParserInfo(stmt.toString(), stmt, (SimpleNode) parser.getASTRoot());
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用{@link CCJSqlParser}解析SQL语句部件返回解析生成的对象,如{@code 'ORDER BY id DESC'}
|
||||
*
|
||||
* @param <T>
|
||||
* @param input
|
||||
* @param method 指定调用的{@link CCJSqlParser}解析方法
|
||||
* @param targetType 返回的解析对象类型
|
||||
* @return
|
||||
* @since 3.18.3
|
||||
*/
|
||||
public static <T> T parseComponent(String input, String method, Class<T> targetType) {
|
||||
try {
|
||||
CCJSqlParser parser = new CCJSqlParser(new StringProvider(input));
|
||||
try {
|
||||
return checkNotNull(targetType, "targetType is null").cast(parser.getClass().getMethod(method).invoke(parser));
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwables.throwIfUnchecked(e.getTargetException());
|
||||
throw new RuntimeException(e.getTargetException());
|
||||
}
|
||||
} catch (IllegalAccessException | NoSuchMethodException | SecurityException e) {
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果{@link Column}没有定义table,且字段名为true/false(不区分大小写)则视为布尔常量
|
||||
*
|
||||
* @param column
|
||||
*/
|
||||
public static boolean isBoolean(Column column) {
|
||||
return null != column && null == column.getTable() &&
|
||||
Pattern.compile("(true|false)", Pattern.CASE_INSENSITIVE).matcher(column.getColumnName()).matches();
|
||||
}
|
||||
|
||||
public static class SqlParserInfo {
|
||||
public String nativeSql;
|
||||
public Statement statement;
|
||||
public SimpleNode simpleNode;
|
||||
|
||||
SqlParserInfo(String nativeSql, Statement statement, SimpleNode simpleNode) {
|
||||
this.nativeSql = nativeSql;
|
||||
this.statement = statement;
|
||||
this.simpleNode = simpleNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package org.jeecg.common.util.sqlInjection.parse;
|
||||
|
||||
import net.sf.jsqlparser.util.TablesNamesFinder;
|
||||
|
||||
/**
|
||||
* SQL语句分析转换器基类<br>
|
||||
* 基于SQL语法对象实现对SQL的修改
|
||||
* (暂时用不到)
|
||||
*
|
||||
* @author guyadong
|
||||
* @since 3.17.0
|
||||
*/
|
||||
public class SqlSyntaxNormalizer extends TablesNamesFinder {
|
||||
protected static final ThreadLocal<Boolean> changed = new ThreadLocal<>();
|
||||
|
||||
public SqlSyntaxNormalizer() {
|
||||
super();
|
||||
init(true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 语句改变返回{@code true},否则返回{@code false}
|
||||
*/
|
||||
public boolean changed() {
|
||||
return Boolean.TRUE.equals(changed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* 复位线程局部变量{@link #changed}状态
|
||||
*/
|
||||
public SqlSyntaxNormalizer resetChanged() {
|
||||
changed.remove();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,255 @@
|
||||
package org.jeecg.common.util.sqlparse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.sqlparse.vo.SelectSqlInfo;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 解析所有表名和字段的类
|
||||
*/
|
||||
@Slf4j
|
||||
public class JSqlParserAllTableManager {
|
||||
|
||||
private final String sql;
|
||||
private final Map<String, SelectSqlInfo> allTableMap = new HashMap<>();
|
||||
/**
|
||||
* 别名对应实际表名
|
||||
*/
|
||||
private final Map<String, String> tableAliasMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 解析后的sql
|
||||
*/
|
||||
private String parsedSql = null;
|
||||
|
||||
JSqlParserAllTableManager(String selectSql) {
|
||||
this.sql = selectSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始解析
|
||||
*
|
||||
* @return
|
||||
* @throws JSQLParserException
|
||||
*/
|
||||
public Map<String, SelectSqlInfo> parse() throws JSQLParserException {
|
||||
// 1. 创建解析器
|
||||
CCJSqlParserManager mgr = new CCJSqlParserManager();
|
||||
// 2. 使用解析器解析sql生成具有层次结构的java类
|
||||
Statement stmt = mgr.parse(new StringReader(this.sql));
|
||||
if (stmt instanceof Select) {
|
||||
Select selectStatement = (Select) stmt;
|
||||
SelectBody selectBody = selectStatement.getSelectBody();
|
||||
this.parsedSql = selectBody.toString();
|
||||
// 3. 解析select查询sql的信息
|
||||
if (selectBody instanceof PlainSelect) {
|
||||
PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
// 4. 合并 fromItems
|
||||
List<FromItem> fromItems = new ArrayList<>();
|
||||
fromItems.add(plainSelect.getFromItem());
|
||||
// 4.1 处理join的表
|
||||
List<Join> joins = plainSelect.getJoins();
|
||||
if (joins != null) {
|
||||
joins.forEach(join -> fromItems.add(join.getRightItem()));
|
||||
}
|
||||
// 5. 处理 fromItems
|
||||
for (FromItem fromItem : fromItems) {
|
||||
// 5.1 通过表名的方式from
|
||||
if (fromItem instanceof Table) {
|
||||
this.addSqlInfoByTable((Table) fromItem);
|
||||
}
|
||||
// 5.2 通过子查询的方式from
|
||||
else if (fromItem instanceof SubSelect) {
|
||||
this.handleSubSelect((SubSelect) fromItem);
|
||||
}
|
||||
}
|
||||
// 6. 解析 selectFields
|
||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
for (SelectItem selectItem : selectItems) {
|
||||
// 6.1 查询的是全部字段
|
||||
if (selectItem instanceof AllColumns) {
|
||||
// 当 selectItem 为 AllColumns 时,fromItem 必定为 Table
|
||||
String tableName = plainSelect.getFromItem(Table.class).getName();
|
||||
// 此处必定不为空,因为在解析 fromItem 时,已经将表名添加到 allTableMap 中
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
assert sqlInfo != null;
|
||||
// 设置为查询全部字段
|
||||
sqlInfo.setSelectAll(true);
|
||||
sqlInfo.setSelectFields(null);
|
||||
sqlInfo.setRealSelectFields(null);
|
||||
}
|
||||
// 6.2 查询的是带表别名( u.* )的全部字段
|
||||
else if (selectItem instanceof AllTableColumns) {
|
||||
AllTableColumns allTableColumns = (AllTableColumns) selectItem;
|
||||
String aliasName = allTableColumns.getTable().getName();
|
||||
// 通过别名获取表名
|
||||
String tableName = this.tableAliasMap.get(aliasName);
|
||||
if (tableName == null) {
|
||||
tableName = aliasName;
|
||||
}
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(tableName);
|
||||
// 如果此处为空,则说明该字段是通过子查询获取的,所以可以不处理,只有实际表才需要处理
|
||||
if (sqlInfo != null) {
|
||||
// 设置为查询全部字段
|
||||
sqlInfo.setSelectAll(true);
|
||||
sqlInfo.setSelectFields(null);
|
||||
sqlInfo.setRealSelectFields(null);
|
||||
}
|
||||
}
|
||||
// 6.3 各种字段表达式处理
|
||||
else if (selectItem instanceof SelectExpressionItem) {
|
||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
Expression expression = selectExpressionItem.getExpression();
|
||||
Alias alias = selectExpressionItem.getAlias();
|
||||
this.handleExpression(expression, alias, plainSelect.getFromItem());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("暂时尚未处理该类型的 SelectBody: {}", selectBody.getClass().getName());
|
||||
throw new JeecgBootException("暂时尚未处理该类型的 SelectBody");
|
||||
}
|
||||
} else {
|
||||
// 非 select 查询sql,不做处理
|
||||
throw new JeecgBootException("非 select 查询sql,不做处理");
|
||||
}
|
||||
return this.allTableMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理子查询
|
||||
*
|
||||
* @param subSelect
|
||||
*/
|
||||
private void handleSubSelect(SubSelect subSelect) {
|
||||
try {
|
||||
String subSelectSql = subSelect.getSelectBody().toString();
|
||||
// 递归调用解析
|
||||
Map<String, SelectSqlInfo> map = JSqlParserUtils.parseAllSelectTable(subSelectSql);
|
||||
if (map != null) {
|
||||
this.assignMap(map);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析子查询出错", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理查询字段表达式
|
||||
*
|
||||
* @param expression
|
||||
*/
|
||||
private void handleExpression(Expression expression, Alias alias, FromItem fromItem) {
|
||||
// 处理函数式字段 CONCAT(name,'(',age,')')
|
||||
if (expression instanceof Function) {
|
||||
Function functionExp = (Function) expression;
|
||||
List<Expression> expressions = functionExp.getParameters().getExpressions();
|
||||
for (Expression expItem : expressions) {
|
||||
this.handleExpression(expItem, null, fromItem);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 处理字段上的子查询
|
||||
if (expression instanceof SubSelect) {
|
||||
this.handleSubSelect((SubSelect) expression);
|
||||
return;
|
||||
}
|
||||
// 不处理字面量
|
||||
if (expression instanceof StringValue ||
|
||||
expression instanceof NullValue ||
|
||||
expression instanceof LongValue ||
|
||||
expression instanceof DoubleValue ||
|
||||
expression instanceof HexValue ||
|
||||
expression instanceof DateValue ||
|
||||
expression instanceof TimestampValue ||
|
||||
expression instanceof TimeValue
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理字段
|
||||
if (expression instanceof Column) {
|
||||
Column column = (Column) expression;
|
||||
// 查询字段名
|
||||
String fieldName = column.getColumnName();
|
||||
String aliasName = fieldName;
|
||||
if (alias != null) {
|
||||
aliasName = alias.getName();
|
||||
}
|
||||
String tableName;
|
||||
if (column.getTable() != null) {
|
||||
// 通过列的表名获取 sqlInfo
|
||||
// 例如 user.name,这里的 tableName 就是 user
|
||||
tableName = column.getTable().getName();
|
||||
// 有可能是别名,需要转换为真实表名
|
||||
if (this.tableAliasMap.get(tableName) != null) {
|
||||
tableName = this.tableAliasMap.get(tableName);
|
||||
}
|
||||
} else {
|
||||
// 当column的table为空时,说明是 fromItem 中的字段
|
||||
tableName = ((Table) fromItem).getName();
|
||||
}
|
||||
SelectSqlInfo $sqlInfo = this.allTableMap.get(tableName);
|
||||
if ($sqlInfo != null) {
|
||||
$sqlInfo.addSelectField(aliasName, fieldName);
|
||||
} else {
|
||||
log.warn("发生意外情况,未找到表名为 {} 的 SelectSqlInfo", tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据表名添加sqlInfo
|
||||
*
|
||||
* @param table
|
||||
*/
|
||||
private void addSqlInfoByTable(Table table) {
|
||||
String tableName = table.getName();
|
||||
// 解析 aliasName
|
||||
if (table.getAlias() != null) {
|
||||
this.tableAliasMap.put(table.getAlias().getName(), tableName);
|
||||
}
|
||||
SelectSqlInfo sqlInfo = new SelectSqlInfo(this.parsedSql);
|
||||
sqlInfo.setFromTableName(table.getName());
|
||||
this.allTableMap.put(sqlInfo.getFromTableName(), sqlInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并map
|
||||
*
|
||||
* @param source
|
||||
*/
|
||||
private void assignMap(Map<String, SelectSqlInfo> source) {
|
||||
for (Map.Entry<String, SelectSqlInfo> entry : source.entrySet()) {
|
||||
SelectSqlInfo sqlInfo = this.allTableMap.get(entry.getKey());
|
||||
if (sqlInfo == null) {
|
||||
this.allTableMap.put(entry.getKey(), entry.getValue());
|
||||
} else {
|
||||
// 合并
|
||||
if (sqlInfo.getSelectFields() == null) {
|
||||
sqlInfo.setSelectFields(entry.getValue().getSelectFields());
|
||||
} else {
|
||||
sqlInfo.getSelectFields().addAll(entry.getValue().getSelectFields());
|
||||
}
|
||||
if (sqlInfo.getRealSelectFields() == null) {
|
||||
sqlInfo.setRealSelectFields(entry.getValue().getRealSelectFields());
|
||||
} else {
|
||||
sqlInfo.getRealSelectFields().addAll(entry.getValue().getRealSelectFields());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user