mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 11:07:06 +00:00
first commit
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
target/
|
||||
.idea/
|
||||
.vscode/
|
||||
logs/
|
||||
log/
|
||||
*.DS_Store
|
||||
*.iml
|
||||
*.bin
|
||||
*.log
|
||||
*.tar.gz
|
||||
*.lib
|
||||
assembly/runtime/*
|
||||
**/dist/
|
||||
*.umi/
|
||||
/assembly/deploy
|
||||
10
CHANGELOG
Normal file
10
CHANGELOG
Normal file
@@ -0,0 +1,10 @@
|
||||
davinci 0.3.0 change log
|
||||
1) add data portal
|
||||
2) add metric trend chart
|
||||
3) add feedback component
|
||||
4) add tab component
|
||||
5) add page setting
|
||||
6) modify permission process
|
||||
7) optimize css style
|
||||
8) optimize filter
|
||||
9) delete view module
|
||||
468
LICENSE
Normal file
468
LICENSE
Normal file
@@ -0,0 +1,468 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Tencent Music Entertainment
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
|
||||
Other dependencies and licenses:
|
||||
|
||||
|
||||
Open Source Software Licensed under the MIT License:
|
||||
--------------------------------------------------------------------
|
||||
1. Mybatis-PageHelper 1.2.10
|
||||
Copyright (c) 2014-2022 abel533@gmail.com
|
||||
|
||||
2. lombok
|
||||
Copyright (C) 2009-2021 The Project Lombok Authors.
|
||||
|
||||
3. react
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
4. ant-design
|
||||
Copyright (c) 2015-present Ant UED, https://xtech.antfin.com/
|
||||
|
||||
5. ant-design-pro
|
||||
Copyright (c) 2019 Alipay.inc
|
||||
|
||||
6. @ant-design/charts
|
||||
Copyright (c) 2021 Ant Design
|
||||
|
||||
7. @ant-design/icons
|
||||
Copyright (c) 2018-present Ant UED, https://xtech.antfin.com/
|
||||
|
||||
8. @antv/layout
|
||||
Copyright (c) 2018 Alipay.inc
|
||||
|
||||
9. @antv/xflow
|
||||
Copyright (c) 2021-2023 Alipay.inc
|
||||
|
||||
10. umi
|
||||
Copyright (c) 2017-present ChenCheng (sorrycc@gmail.com)
|
||||
|
||||
11. @umijs/route-utils
|
||||
Copyright (c) 2019-present chenshuai2144 (qixian.cs@outlook.com)
|
||||
|
||||
12. ahooks
|
||||
Copyright (c) 2020 ahooks
|
||||
|
||||
13. axios
|
||||
Copyright (c) 2014-present Matt Zabriskie & Collaborators
|
||||
|
||||
14. classnames
|
||||
Copyright (c) 2018 Jed Watson
|
||||
|
||||
15. crypto-js
|
||||
Copyright (c) 2009-2013 Jeff Mott
|
||||
Copyright (c) 2013-2016 Evan Vosberg
|
||||
|
||||
16. immutability-helper
|
||||
Copyright (c) 2017 Moshe Kolodny
|
||||
|
||||
17. lodash
|
||||
Copyright JS Foundation and other contributors <https://js.foundation/>
|
||||
|
||||
18. moment
|
||||
Copyright (c) JS Foundation and other contributors
|
||||
|
||||
19. numeral
|
||||
Copyright (c) 2016 Adam Draper
|
||||
|
||||
20. omit.js
|
||||
Copyright (c) 2016 Benjy Cui
|
||||
|
||||
21. rc-menu
|
||||
Copyright (c) 2014-present yiminghe
|
||||
|
||||
22. rc-util
|
||||
Copyright (c) 2014-present yiminghe
|
||||
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
|
||||
|
||||
23. react-ace
|
||||
Copyright (c) 2014 James Hrisho
|
||||
|
||||
24. react-dev-inspector
|
||||
Copyright (c) zthxxx (https://blog.zthxxx.me)
|
||||
|
||||
25. react-lazyload
|
||||
Copyright (c) 2015 Sen Yang
|
||||
|
||||
26. react-spinners
|
||||
Copyright (c) 2017 David Hu
|
||||
|
||||
27.react-split-pane
|
||||
Copyright (c) 2015 tomkp
|
||||
|
||||
28. snappyjs
|
||||
Copyright (c) 2016 Zhipeng Jia
|
||||
|
||||
29. sql-formatter
|
||||
Copyright (c) 2016-2020 ZeroTurnaround LLC
|
||||
Copyright (c) 2020-2021 George Leslie-Waksman and other contributors
|
||||
Copyright (c) 2021-Present inferrinizzard and other contributors
|
||||
|
||||
30. @ant-design/pro-cli
|
||||
Copyright (c) 2017-2018 Alipay
|
||||
|
||||
31. cross-env
|
||||
Copyright (c) 2017 Kent C. Dodds
|
||||
|
||||
32.cross-port-killer
|
||||
Copyright (c) 2017 Rafael Milewski
|
||||
|
||||
33.detect-installer
|
||||
Copyright (c) 2019-present chenshuai2144 (qixian.cs@outlook.com)
|
||||
|
||||
34.eslint
|
||||
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
||||
|
||||
35.express
|
||||
Copyright (c) 2009-2014 TJ Holowaychuk <tj@vision-media.ca>
|
||||
Copyright (c) 2013-2014 Roman Shtylman <shtylman+expressjs@gmail.com>
|
||||
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
||||
|
||||
36.gh-pages
|
||||
Copyright (c) 2014 Tim Schaub
|
||||
|
||||
37.inflect
|
||||
Copyright (C) 2020 Pavan Kumar Sunkara
|
||||
|
||||
38.lint-staged
|
||||
Copyright (c) 2016 Andrey Okonetchnikov
|
||||
|
||||
39.prettier
|
||||
Copyright © James Long and contributors
|
||||
|
||||
40.stylelint
|
||||
Copyright (c) 2015 - present Maxime Thirouin, David Clark & Richard Hallows
|
||||
|
||||
41.umi-serve
|
||||
Copyright (c) 2017-present ChenCheng (sorrycc@gmail.com)
|
||||
|
||||
42.webpack
|
||||
Copyright JS Foundation and other contributors
|
||||
|
||||
43.react-dnd
|
||||
Copyright (c) 2015 Dan Abramov
|
||||
|
||||
44.react-grid-layout
|
||||
Copyright (c) 2016 Samuel Reed
|
||||
|
||||
45.slat
|
||||
Copyright © 2016–2023, Ian Storm Taylor
|
||||
|
||||
46.html2canvas
|
||||
Copyright (c) 2012 Niklas von Hertzen
|
||||
|
||||
47.core-js
|
||||
Copyright (c) 2014-2020 Denis Pushkarev
|
||||
|
||||
48.immer 4.0.2
|
||||
Copyright (c) 2017 Michel Weststrate
|
||||
|
||||
49.redux
|
||||
Copyright (c) 2015-present Dan Abramov
|
||||
The Redux logo is dedicated to the public domain and licensed under CC0.
|
||||
|
||||
50.redux-saga
|
||||
Copyright (c) 2015 Yassine Elouafi
|
||||
The Redux-Saga logo is dedicated to the public domain and licensed under CC0.
|
||||
|
||||
51.ts-loader
|
||||
Copyright (c) 2015 TypeStrong
|
||||
|
||||
52.minimist
|
||||
Files:https://github.com/minimistjs/minimist/tree/v1.2.3
|
||||
License Details:https://github.com/minimistjs/minimist/blob/main/LICENSE
|
||||
|
||||
53.intl
|
||||
copyright (c) 2013 Andy Earnshaw
|
||||
|
||||
|
||||
A copy of the MIT License is included in this file.
|
||||
|
||||
|
||||
Open Source Software Licensed under the Apache License Version 2.0:
|
||||
--------------------------------------------------------------------
|
||||
1. HanLP
|
||||
Files: https://github.com/hankcs/HanLP/tree/v1.8.3
|
||||
License Details: https://github.com/hankcs/HanLP/blob/v1.8.3/LICENSE
|
||||
|
||||
2. mybatis
|
||||
iBATIS
|
||||
This product includes software developed by
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
Copyright 2010 The Apache Software Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
3. guava
|
||||
Files: https://github.com/google/guava/tree/v20.0
|
||||
License Details: https://github.com/google/guava/blob/master/LICENSE
|
||||
|
||||
4. hadoop
|
||||
This product includes software developed by The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
5. Jackson
|
||||
Files: https://github.com/FasterXML/jackson-core/tree/2.11
|
||||
License Details: https://github.com/FasterXML/jackson-core/blob/2.11/LICENSE
|
||||
|
||||
6. commons-lang
|
||||
Apache Commons Lang
|
||||
Copyright 2001-2017 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
This product includes software from the Spring Framework,
|
||||
under the Apache License 2.0 (see: StringUtils.containsWhitespace())
|
||||
|
||||
7. testng
|
||||
Files:https://github.com/testng-team/testng/tree/6.13.1
|
||||
License Details:https://github.com/testng-team/testng/blob/6.13.1/LICENSE.txt
|
||||
|
||||
8. jackson-dataformat-yaml
|
||||
Files:https://github.com/FasterXML/jackson-dataformat-yaml/tree/jackson-dataformat-yaml-2.8.11
|
||||
License Details:https://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
|
||||
9. druid
|
||||
Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
10. davinci
|
||||
Licensed to Apereo under one or more contributor license
|
||||
agreements. See the NOTICE file distributed with this work
|
||||
for additional information regarding copyright ownership.
|
||||
Apereo licenses this file to you under the Apache License,
|
||||
Version 2.0 (the "License"); you may not use this file
|
||||
except in compliance with the License. You may obtain a
|
||||
copy of the License at the following location:
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
11. echarts
|
||||
Apache ECharts
|
||||
Copyright 2017-2023 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (https://www.apache.org/).
|
||||
|
||||
12. echarts-wordcloud
|
||||
Apache ECharts
|
||||
Copyright 2017-2023 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (https://www.apache.org/).
|
||||
|
||||
13. carlo
|
||||
Files:https://github.com/GoogleChromeLabs/carlo
|
||||
License Details:https://github.com/GoogleChromeLabs/carlo/blob/master/LICENSE
|
||||
|
||||
14. puppeteer-core
|
||||
Copyright 2017 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
15. swagger-ui-react
|
||||
swagger-ui
|
||||
Copyright 2020-2021 SmartBear Software Inc.
|
||||
|
||||
16. typescript
|
||||
files:https://github.com/microsoft/TypeScript
|
||||
License Details:https://github.com/microsoft/TypeScript/blob/main/LICENSE.txt
|
||||
|
||||
17. io.jsonwebtoken
|
||||
Copyright (C) 2014 jsonwebtoken.io
|
||||
Files: https://repo1.maven.org/maven2/io/jsonwebtoken/jjwt/0.9.1/jjwt-0.9.1.jar
|
||||
|
||||
|
||||
Terms of the Apache License Version 2.0:
|
||||
--------------------------------------------------------------------
|
||||
Apache License
|
||||
|
||||
Version 2.0, January 2004
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
|
||||
Open Source Software Licensed under the Modified BSD License:
|
||||
--------------------------------------------------------------------
|
||||
1. node-sha1
|
||||
Copyright © 2009, Jeff Mott. All rights reserved.
|
||||
Copyright © 2011, Paul Vorbach. All rights reserved.
|
||||
|
||||
This project is licensed under the terms of the Modified BSD License, as follows:
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2005-2023, NumPy Developers.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
Neither the name oCrypto-JS nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
2. ace-builds
|
||||
Copyright (c) 2010, Ajax.org B.V.
|
||||
All rights reserved.
|
||||
|
||||
This project is licensed under the terms of the Modified BSD License, as follows:
|
||||
-------------------------------------------------------------------
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Ajax.org B.V. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Other Open Source Software:
|
||||
--------------------------------------------------------------------
|
||||
1. jsencrypt
|
||||
Files:https://github.com/travist/jsencrypt
|
||||
License Details:https://github.com/travist/jsencrypt/blob/master/LICENSE.txt
|
||||
|
||||
|
||||
|
||||
|
||||
50
README.md
Normal file
50
README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
English | [中文](README_CN.md)
|
||||
|
||||
# SuperSonic (超音数)
|
||||
|
||||
**SuperSonic is an out-of-the-box yet highly extensible framework for building a data chatbot**. SuperSonic provides a chat interface that empowers users to query data using natural language and visualize the results with suitable charts. To enable such experience, the only thing necessary is to build a logical semantic model (definition of metrics, dimensions, relationships, etc) on top of the physical data stores, and no data modification or copying is required. Meanwhile SuperSonic is designed to be plug-and-play, allowing new functionalities to be added through plugins and core components to be integrated into other systems.
|
||||
|
||||
## Motivation
|
||||
|
||||
The emergence of Large Language Models (LLMs) like ChatGPT is reshaping the way information is retrieved. In the field of data analytics, both academia and industry are primarily focused on leveraging deep learning models to convert natural language queries into SQL queries. While some works show promising results, they are not applicable to real-world scenarios.
|
||||
|
||||
From our perspective, the key to filling the real-world gap lies in two aspects:
|
||||
1. Utilize a combination of rule-based and model-based semantic parsers to deal with different scenarios
|
||||
2. Introduce a semantic model layer to encapsulate underlying complexity thus simplify the semantic parsers
|
||||
|
||||
With these ideas in mind, we developed SuperSonic as a reference implementation and used it to power our real-world products. Additionally, to encourage further development of data chatbots, we decided to open source SuperSonic as an extensible framework.
|
||||
|
||||
## Out-of-the-box Features
|
||||
|
||||
- Built-in graphical interface for business users to enter data queries
|
||||
- Built-in graphical interface for analytics engineers to manage semantic models
|
||||
- Support input auto-completion as well as query recommendation
|
||||
- Support multi-turn conversation and switch context automatically
|
||||
- Support three-level permission control: domain-level, column-level and row-level
|
||||
|
||||
## Extensible Components
|
||||
|
||||
SuperSonic contains four core components, each of which can be extended or integrated:
|
||||
|
||||
<img src="./docs/images/supersonic_components.png" height="50%" width="50%" align="center"/>
|
||||
|
||||
- **Chat interface:** accepts user queries and answer results with approriate visualization charts. It supports input auto-completion as well as multi-turn conversation.
|
||||
|
||||
- **Schema mapper:** identifies references to schema elements in natural language queries. It matches queries against the knowledage base which is constructed using the schema of semantic models.
|
||||
|
||||
- **Semantic parser chain:** resolves query mode and choose the most suitable semantic model. It is composed of a group of rule-based and model-based parsers, each of which deals with specific scenarios.
|
||||
|
||||
- **Semantic model layer:** manages semantic models and generate SQL statement given specific semantic model and related semantic items. It encapsulates technical concepts, calculation formulas and entity relationships of the underlying data.
|
||||
|
||||
## Quick Demo
|
||||
|
||||
SuperSonic comes with a sample semantic data model as well as sample chat that can be used as a starting point. Please follow the steps:
|
||||
|
||||
- Download the latest prebuilt binary from the release page
|
||||
- Run script "bin/start-all.sh" to start services
|
||||
- Visit http://localhost:9080 in browser to explore chat interface
|
||||
- Visit http://localhost:9081 in browser to explore modeling interface
|
||||
|
||||
## How to Build
|
||||
|
||||
Download the source code and run script "assembly/bin/build-all.sh" to build both front-end webapp and back-end services
|
||||
48
README_CN.md
Normal file
48
README_CN.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 超音数(SuperSonic)
|
||||
|
||||
**超音数是一个开箱即用且易于扩展的数据问答对话框架**。通过超音数的问答对话界面,用户能够使用自然语言查询数据,系统会选择合适的可视化图表呈现结果。超音数不需要修改或复制数据,只需要在物理数据库之上构建逻辑语义模型(定义指标、维度、相互间关系等),即可开启数据问答体验。与此同时,超音数被设计为可插拔式框架,允许以插件形式来扩展新功能,或者将核心组件与其他系统集成。
|
||||
|
||||
## 项目动机
|
||||
|
||||
大型语言模型(LLMs)如ChatGPT的出现正在重塑信息检索的方式。在数据分析领域,学术界和工业界主要关注利用深度学习模型将自然语言查询转换为SQL查询。虽然一些工作显示出有前景的结果,但它们还并不适用于实际场景。
|
||||
|
||||
在我们看来,为了在实际场景发挥价值,有两个关键点:
|
||||
1. 将基于规则和基于模型的语义解析器相结合,发挥各自优势,以便处理不同的场景
|
||||
2. 引入语义模型层来封装数据底层的复杂性,从而简化语义解析器的问题求解空间
|
||||
|
||||
为了验证上述想法,我们开发了超音数项目,并将其应用在实际的内部产品中。与此同时,我们决定将超音数作为一个可扩展的框架开源,希望能够促进数据问答对话领域的进一步发展。
|
||||
|
||||
## 开箱即用的特性
|
||||
|
||||
- 内置图形界面以便业务用户输入数据查询
|
||||
- 内置图形界面以便分析工程师管理语义模型
|
||||
- 支持文本输入的联想和查询问题的推荐
|
||||
- 支持多轮对话,根据语境自动切换上下文
|
||||
- 支持三级权限控制:主题域级、列级、行级
|
||||
|
||||
## 易于扩展的组件
|
||||
|
||||
超音数包含四个核心组件,每个都易于扩展或被集成:
|
||||
|
||||
<img src="./docs/images/supersonic_components.png" height="50%" width="50%" align="center"/>
|
||||
|
||||
- **问答对话界面(chat interface)**:接受用户查询并选择合适的可视化图表呈现结果,支持输入联想和多轮对话。
|
||||
|
||||
- **模式映射器(schema mapper)**:基于语义模型的schema构建知识库,然后将自然语言查询在知识库中进行匹配,为后续的语义解析提供相关信息。
|
||||
|
||||
- **语义解析器链(semantic parser chain)**:识别查询模式并选择最匹配的语义模型,其由一组基于规则或模型的解析器组成,每个解析器可用于应对不同的特定场景。
|
||||
|
||||
- **语义模型层(semantic model layer)**:建模阶段,负责构建与管理语义模型;查询阶段,依据给定的语义模型来生成SQL语句。
|
||||
|
||||
## 快速体验
|
||||
|
||||
超音数自带样例的语义模型和问答对话,只需以下三步即可快速体验:
|
||||
|
||||
- 从release page下载预先构建好的发行包
|
||||
- 运行 "bin/start-all.sh"启动前后端服务
|
||||
- 在浏览器访问http://localhost:9080 开启数据问答探索
|
||||
- 在浏览器访问http://localhost:9081 开启语义建模探索
|
||||
|
||||
## 如何构建
|
||||
|
||||
下载源码包,运行脚本"assembly/bin/build-all.sh",会将前后端一起编译打包
|
||||
24
assembly/bin/build-all.sh
Executable file
24
assembly/bin/build-all.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sbinDir=$(cd "$(dirname "$0")"; pwd)
|
||||
baseDir=$(readlink -f $sbinDir/../)
|
||||
runtimeDir=$baseDir/runtime
|
||||
buildDir=$baseDir/build
|
||||
|
||||
cd $baseDir
|
||||
|
||||
#1. build semantic chat service
|
||||
rm -fr ${buildDir}/*.tar.gz
|
||||
rm -fr dist
|
||||
|
||||
mvn -f $baseDir/../ clean package -DskipTests
|
||||
|
||||
#2. move package to build
|
||||
cp $baseDir/../launchers/chat/target/*.tar.gz ${buildDir}/supersonic-chat.tar.gz
|
||||
cp $baseDir/../launchers/semantic/target/*.tar.gz ${buildDir}/supersonic-semantic.tar.gz
|
||||
|
||||
#3. build webapp
|
||||
chmod +x $baseDir/../webapp/start-fe-prod.sh
|
||||
cd ../webapp
|
||||
sh ./start-fe-prod.sh
|
||||
cp -fr ./supersonic-webapp.tar.gz ${buildDir}/
|
||||
35
assembly/bin/start-all.sh
Executable file
35
assembly/bin/start-all.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sbinDir=$(cd "$(dirname "$0")"; pwd)
|
||||
baseDir=$(readlink -f $sbinDir/../)
|
||||
runtimeDir=$baseDir/runtime
|
||||
buildDir=$baseDir/build
|
||||
|
||||
cd $baseDir
|
||||
|
||||
#1. clear file
|
||||
rm -fr ${runtimeDir}/*
|
||||
|
||||
#2. package lib
|
||||
|
||||
tar -zxvf ${buildDir}/supersonic-semantic.tar.gz -C ${runtimeDir}
|
||||
tar -zxvf ${buildDir}/supersonic-chat.tar.gz -C ${runtimeDir}
|
||||
|
||||
mv ${runtimeDir}/launchers-chat-1.0.0-SNAPSHOT ${runtimeDir}/supersonic-chat
|
||||
mv ${runtimeDir}/launchers-semantic-1.0.0-SNAPSHOT ${runtimeDir}/supersonic-semantic
|
||||
|
||||
tar -zxvf ${buildDir}/supersonic-webapp.tar.gz -C ${buildDir}
|
||||
|
||||
mkdir -p ${runtimeDir}/supersonic-semantic/webapp
|
||||
mkdir -p ${runtimeDir}/supersonic-chat/webapp
|
||||
|
||||
cp -fr ${buildDir}/supersonic-webapp/* ${runtimeDir}/supersonic-semantic/webapp
|
||||
cp -fr ${buildDir}/supersonic-webapp/* ${runtimeDir}/supersonic-chat/webapp
|
||||
|
||||
rm -fr ${buildDir}/supersonic-webapp
|
||||
|
||||
#3. start service
|
||||
sh ${runtimeDir}/supersonic-semantic/bin/service.sh restart
|
||||
sleep 5
|
||||
sh ${runtimeDir}/supersonic-chat/bin/service.sh restart
|
||||
|
||||
39
assembly/build/build.xml
Normal file
39
assembly/build/build.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>bin</id>
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/src/main/bin</directory>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<fileMode>0777</fileMode>
|
||||
<directoryMode>0755</directoryMode>
|
||||
</fileSet>
|
||||
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<outputDirectory>conf</outputDirectory>
|
||||
<fileMode>0777</fileMode>
|
||||
<directoryMode>0755</directoryMode>
|
||||
</fileSet>
|
||||
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}</directory>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<useProjectArtifact>false</useProjectArtifact>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
||||
41
auth/api/pom.xml
Normal file
41
auth/api/pom.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>auth</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>auth-api</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.tencent.supersonic.auth.api.authentication.config;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
public class AuthenticationConfig {
|
||||
|
||||
|
||||
@Value("${authentication.exclude.path:XXX}")
|
||||
private String excludePath;
|
||||
|
||||
@Value("${authentication.enable:false}")
|
||||
private boolean enabled;
|
||||
|
||||
@Value("${authentication.token.secret:secret}")
|
||||
private String tokenSecret;
|
||||
|
||||
@Value("${authentication.token.http.header.key:Auth}")
|
||||
private String tokenHttpHeaderKey;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.tencent.supersonic.auth.api.authentication.constant;
|
||||
|
||||
public class UserConstants {
|
||||
|
||||
public static final String TOKEN_USER_ID = "token_user_id";
|
||||
|
||||
public static final String TOKEN_USER_NAME = "token_user_name";
|
||||
|
||||
public static final String TOKEN_USER_PASSWORD = "token_user_password";
|
||||
|
||||
public static final String TOKEN_USER_DISPLAY_NAME = "token_user_display_name";
|
||||
|
||||
public static final String TOKEN_USER_EMAIL = "token_user_email";
|
||||
|
||||
public static final String TOKEN_ALGORITHM = "HS512";
|
||||
|
||||
public static final String TOKEN_CREATE_TIME = "token_create_time";
|
||||
|
||||
public static final String TOKEN_PREFIX = "Bearer";
|
||||
|
||||
public static final Long TOKEN_TIME_OUT = 25920000000L;
|
||||
|
||||
public static final String INTERNAL = "internal";
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.tencent.supersonic.auth.api.authentication.pojo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class User {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String displayName;
|
||||
|
||||
private String email;
|
||||
|
||||
public static User get(Long id, String name, String displayName, String email) {
|
||||
return new User(id, name, displayName, email);
|
||||
}
|
||||
|
||||
public static User getFakeUser() {
|
||||
return new User(1L, "admin", "admin", "admin@email");
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return StringUtils.isBlank(displayName) ? name : displayName;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.tencent.supersonic.auth.api.authentication.pojo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class UserWithPassword extends User {
|
||||
|
||||
private String password;
|
||||
|
||||
public UserWithPassword(Long id, String name, String displayName, String email, String password) {
|
||||
super(id, name, displayName, email);
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public static UserWithPassword get(Long id, String name, String displayName, String email, String password) {
|
||||
return new UserWithPassword(id, name, displayName, email, password);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.tencent.supersonic.auth.api.authentication.request;
|
||||
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UserReq {
|
||||
|
||||
@NotBlank(message = "name can not be null")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "password can not be null")
|
||||
private String password;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.tencent.supersonic.auth.api.authentication.service;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserService {
|
||||
|
||||
List<String> getUserNames();
|
||||
|
||||
List<User> getUserList();
|
||||
|
||||
void register(UserReq userCmd);
|
||||
|
||||
String login(UserReq userCmd);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.tencent.supersonic.auth.api.authentication.service;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public interface UserStrategy {
|
||||
|
||||
boolean accept(boolean isEnableAuthentication);
|
||||
|
||||
User findUser(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.tencent.supersonic.auth.api.authentication.utils;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.service.UserStrategy;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public final class UserHolder {
|
||||
|
||||
private static UserStrategy REPO;
|
||||
|
||||
public static synchronized void setStrategy(UserStrategy strategy) {
|
||||
REPO = strategy;
|
||||
}
|
||||
|
||||
public static User findUser(HttpServletRequest request, HttpServletResponse response) {
|
||||
return REPO.findUser(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class AuthRes {
|
||||
|
||||
private String domainId;
|
||||
private String name;
|
||||
|
||||
public AuthRes() {
|
||||
}
|
||||
|
||||
public AuthRes(String domainId, String name) {
|
||||
this.domainId = domainId;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.pojo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AuthResGrp {
|
||||
|
||||
private List<AuthRes> group = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.pojo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DimensionFilter {
|
||||
|
||||
private List<String> expressions = new ArrayList<>();
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.request;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AddUsersToGroupReq {
|
||||
|
||||
private Integer groupId;
|
||||
private List<String> users;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.request;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class QueryAuthResReq {
|
||||
|
||||
private String user;
|
||||
private List<AuthRes> resources;
|
||||
private String domainId;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.request;
|
||||
|
||||
|
||||
import com.tencent.supersonic.common.request.PageBaseReq;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryGroupReq extends PageBaseReq {
|
||||
|
||||
private List<Integer> groupIds;
|
||||
private List<String> users;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.request;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RemoveGroupReq {
|
||||
|
||||
private List<Integer> groupIds;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.request;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RemoveUsersFromGroupReq {
|
||||
|
||||
private Integer groupId;
|
||||
private List<String> users;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.response;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authorization.pojo.AuthResGrp;
|
||||
import com.tencent.supersonic.auth.api.authorization.pojo.DimensionFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AuthorizedResourceResp {
|
||||
|
||||
private List<AuthResGrp> resources = new ArrayList<>();
|
||||
|
||||
private List<DimensionFilter> filters = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.tencent.supersonic.auth.api.authorization.service;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
|
||||
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public interface AuthService {
|
||||
|
||||
AuthorizedResourceResp queryAuthorizedResources(HttpServletRequest request, QueryAuthResReq req);
|
||||
}
|
||||
82
auth/authentication/pom.xml
Normal file
82
auth/authentication/pom.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>auth</artifactId>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>auth-authentication</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>${alibaba.druid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>${mybatis-spring.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper</artifactId>
|
||||
<version>${pagehelper.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>auth-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.tencent.supersonic.auth.authentication.application;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
|
||||
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
|
||||
import com.tencent.supersonic.auth.api.authentication.service.UserService;
|
||||
import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO;
|
||||
import com.tencent.supersonic.auth.authentication.domain.repository.UserRepository;
|
||||
import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
private UserRepository userRepository;
|
||||
|
||||
private UserTokenUtils userTokenUtils;
|
||||
|
||||
public UserServiceImpl(UserRepository userRepository, UserTokenUtils userTokenUtils) {
|
||||
this.userRepository = userRepository;
|
||||
this.userTokenUtils = userTokenUtils;
|
||||
}
|
||||
|
||||
|
||||
private List<UserDO> getUserDOList() {
|
||||
return userRepository.getUserList();
|
||||
}
|
||||
|
||||
private UserDO getUser(String name) {
|
||||
return userRepository.getUser(name);
|
||||
}
|
||||
|
||||
public boolean checkExist(UserWithPassword user) {
|
||||
UserDO userDO = getUser(user.getName());
|
||||
if (userDO == null) {
|
||||
return false;
|
||||
}
|
||||
return userDO.getPassword().equals(user.getPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUserNames() {
|
||||
return getUserDOList().stream().map(UserDO::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<User> getUserList() {
|
||||
List<UserDO> userDOS = getUserDOList();
|
||||
return userDOS.stream().map(this::convert).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private User convert(UserDO userDO) {
|
||||
User user = new User();
|
||||
BeanUtils.copyProperties(userDO, user);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void register(UserReq userReq) {
|
||||
List<String> userDOS = getUserNames();
|
||||
if (userDOS.contains(userReq.getName())) {
|
||||
throw new RuntimeException(String.format("user %s exist", userReq.getName()));
|
||||
}
|
||||
UserDO userDO = new UserDO();
|
||||
BeanUtils.copyProperties(userReq, userDO);
|
||||
userRepository.addUser(userDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String login(UserReq userReq) {
|
||||
UserDO userDO = getUser(userReq.getName());
|
||||
if (userDO == null) {
|
||||
throw new RuntimeException("user not exist,please register");
|
||||
}
|
||||
if (userDO.getPassword().equals(userReq.getPassword())) {
|
||||
UserWithPassword user = UserWithPassword.get(userDO.getId(), userDO.getName(), userDO.getDisplayName(),
|
||||
userDO.getEmail(), userDO.getPassword());
|
||||
return userTokenUtils.generateToken(user);
|
||||
}
|
||||
throw new RuntimeException("password not correct, please try again");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.dataobject;
|
||||
|
||||
public class UserDO {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String displayName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* @return id
|
||||
*/
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id
|
||||
*/
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name == null ? null : name.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param password
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password == null ? null : password.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return display_name
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param displayName
|
||||
*/
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName == null ? null : displayName.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return email
|
||||
*/
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param email
|
||||
*/
|
||||
public void setEmail(String email) {
|
||||
this.email = email == null ? null : email.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.dataobject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class UserDOExample {
|
||||
|
||||
/**
|
||||
* s2_user
|
||||
*/
|
||||
protected String orderByClause;
|
||||
|
||||
/**
|
||||
* s2_user
|
||||
*/
|
||||
protected boolean distinct;
|
||||
|
||||
/**
|
||||
* s2_user
|
||||
*/
|
||||
protected List<Criteria> oredCriteria;
|
||||
|
||||
/**
|
||||
* s2_user
|
||||
*/
|
||||
protected Integer limitStart;
|
||||
|
||||
/**
|
||||
* s2_user
|
||||
*/
|
||||
protected Integer limitEnd;
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public UserDOExample() {
|
||||
oredCriteria = new ArrayList<Criteria>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setOrderByClause(String orderByClause) {
|
||||
this.orderByClause = orderByClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public String getOrderByClause() {
|
||||
return orderByClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setDistinct(boolean distinct) {
|
||||
this.distinct = distinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public boolean isDistinct() {
|
||||
return distinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public List<Criteria> getOredCriteria() {
|
||||
return oredCriteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void or(Criteria criteria) {
|
||||
oredCriteria.add(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public Criteria or() {
|
||||
Criteria criteria = createCriteriaInternal();
|
||||
oredCriteria.add(criteria);
|
||||
return criteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public Criteria createCriteria() {
|
||||
Criteria criteria = createCriteriaInternal();
|
||||
if (oredCriteria.size() == 0) {
|
||||
oredCriteria.add(criteria);
|
||||
}
|
||||
return criteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
protected Criteria createCriteriaInternal() {
|
||||
Criteria criteria = new Criteria();
|
||||
return criteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void clear() {
|
||||
oredCriteria.clear();
|
||||
orderByClause = null;
|
||||
distinct = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setLimitStart(Integer limitStart) {
|
||||
this.limitStart = limitStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public Integer getLimitStart() {
|
||||
return limitStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setLimitEnd(Integer limitEnd) {
|
||||
this.limitEnd = limitEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
public Integer getLimitEnd() {
|
||||
return limitEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* s2_user null
|
||||
*/
|
||||
protected abstract static class GeneratedCriteria {
|
||||
|
||||
protected List<Criterion> criteria;
|
||||
|
||||
protected GeneratedCriteria() {
|
||||
super();
|
||||
criteria = new ArrayList<Criterion>();
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return criteria.size() > 0;
|
||||
}
|
||||
|
||||
public List<Criterion> getAllCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public List<Criterion> getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
protected void addCriterion(String condition) {
|
||||
if (condition == null) {
|
||||
throw new RuntimeException("Value for condition cannot be null");
|
||||
}
|
||||
criteria.add(new Criterion(condition));
|
||||
}
|
||||
|
||||
protected void addCriterion(String condition, Object value, String property) {
|
||||
if (value == null) {
|
||||
throw new RuntimeException("Value for " + property + " cannot be null");
|
||||
}
|
||||
criteria.add(new Criterion(condition, value));
|
||||
}
|
||||
|
||||
protected void addCriterion(String condition, Object value1, Object value2, String property) {
|
||||
if (value1 == null || value2 == null) {
|
||||
throw new RuntimeException("Between values for " + property + " cannot be null");
|
||||
}
|
||||
criteria.add(new Criterion(condition, value1, value2));
|
||||
}
|
||||
|
||||
public Criteria andIdIsNull() {
|
||||
addCriterion("id is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdIsNotNull() {
|
||||
addCriterion("id is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdEqualTo(Long value) {
|
||||
addCriterion("id =", value, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdNotEqualTo(Long value) {
|
||||
addCriterion("id <>", value, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdGreaterThan(Long value) {
|
||||
addCriterion("id >", value, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdGreaterThanOrEqualTo(Long value) {
|
||||
addCriterion("id >=", value, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdLessThan(Long value) {
|
||||
addCriterion("id <", value, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdLessThanOrEqualTo(Long value) {
|
||||
addCriterion("id <=", value, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdIn(List<Long> values) {
|
||||
addCriterion("id in", values, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdNotIn(List<Long> values) {
|
||||
addCriterion("id not in", values, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdBetween(Long value1, Long value2) {
|
||||
addCriterion("id between", value1, value2, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdNotBetween(Long value1, Long value2) {
|
||||
addCriterion("id not between", value1, value2, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameIsNull() {
|
||||
addCriterion("name is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameIsNotNull() {
|
||||
addCriterion("name is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameEqualTo(String value) {
|
||||
addCriterion("name =", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameNotEqualTo(String value) {
|
||||
addCriterion("name <>", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameGreaterThan(String value) {
|
||||
addCriterion("name >", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameGreaterThanOrEqualTo(String value) {
|
||||
addCriterion("name >=", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameLessThan(String value) {
|
||||
addCriterion("name <", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameLessThanOrEqualTo(String value) {
|
||||
addCriterion("name <=", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameLike(String value) {
|
||||
addCriterion("name like", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameNotLike(String value) {
|
||||
addCriterion("name not like", value, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameIn(List<String> values) {
|
||||
addCriterion("name in", values, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameNotIn(List<String> values) {
|
||||
addCriterion("name not in", values, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameBetween(String value1, String value2) {
|
||||
addCriterion("name between", value1, value2, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andNameNotBetween(String value1, String value2) {
|
||||
addCriterion("name not between", value1, value2, "name");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordIsNull() {
|
||||
addCriterion("password is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordIsNotNull() {
|
||||
addCriterion("password is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordEqualTo(String value) {
|
||||
addCriterion("password =", value, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordNotEqualTo(String value) {
|
||||
addCriterion("password <>", value, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordGreaterThan(String value) {
|
||||
addCriterion("password >", value, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordGreaterThanOrEqualTo(String value) {
|
||||
addCriterion("password >=", value, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordLessThan(String value) {
|
||||
addCriterion("password <", value, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordLessThanOrEqualTo(String value) {
|
||||
addCriterion("password <=", value, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordLike(String value) {
|
||||
addCriterion("password like", value, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordNotLike(String value) {
|
||||
addCriterion("password not like", value, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordIn(List<String> values) {
|
||||
addCriterion("password in", values, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordNotIn(List<String> values) {
|
||||
addCriterion("password not in", values, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordBetween(String value1, String value2) {
|
||||
addCriterion("password between", value1, value2, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPasswordNotBetween(String value1, String value2) {
|
||||
addCriterion("password not between", value1, value2, "password");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameIsNull() {
|
||||
addCriterion("display_name is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameIsNotNull() {
|
||||
addCriterion("display_name is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameEqualTo(String value) {
|
||||
addCriterion("display_name =", value, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameNotEqualTo(String value) {
|
||||
addCriterion("display_name <>", value, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameGreaterThan(String value) {
|
||||
addCriterion("display_name >", value, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameGreaterThanOrEqualTo(String value) {
|
||||
addCriterion("display_name >=", value, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameLessThan(String value) {
|
||||
addCriterion("display_name <", value, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameLessThanOrEqualTo(String value) {
|
||||
addCriterion("display_name <=", value, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameLike(String value) {
|
||||
addCriterion("display_name like", value, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameNotLike(String value) {
|
||||
addCriterion("display_name not like", value, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameIn(List<String> values) {
|
||||
addCriterion("display_name in", values, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameNotIn(List<String> values) {
|
||||
addCriterion("display_name not in", values, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameBetween(String value1, String value2) {
|
||||
addCriterion("display_name between", value1, value2, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andDisplayNameNotBetween(String value1, String value2) {
|
||||
addCriterion("display_name not between", value1, value2, "displayName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailIsNull() {
|
||||
addCriterion("email is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailIsNotNull() {
|
||||
addCriterion("email is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailEqualTo(String value) {
|
||||
addCriterion("email =", value, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailNotEqualTo(String value) {
|
||||
addCriterion("email <>", value, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailGreaterThan(String value) {
|
||||
addCriterion("email >", value, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailGreaterThanOrEqualTo(String value) {
|
||||
addCriterion("email >=", value, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailLessThan(String value) {
|
||||
addCriterion("email <", value, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailLessThanOrEqualTo(String value) {
|
||||
addCriterion("email <=", value, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailLike(String value) {
|
||||
addCriterion("email like", value, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailNotLike(String value) {
|
||||
addCriterion("email not like", value, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailIn(List<String> values) {
|
||||
addCriterion("email in", values, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailNotIn(List<String> values) {
|
||||
addCriterion("email not in", values, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailBetween(String value1, String value2) {
|
||||
addCriterion("email between", value1, value2, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andEmailNotBetween(String value1, String value2) {
|
||||
addCriterion("email not between", value1, value2, "email");
|
||||
return (Criteria) this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* s2_user
|
||||
*/
|
||||
public static class Criteria extends GeneratedCriteria {
|
||||
|
||||
protected Criteria() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* s2_user null
|
||||
*/
|
||||
public static class Criterion {
|
||||
|
||||
private String condition;
|
||||
|
||||
private Object value;
|
||||
|
||||
private Object secondValue;
|
||||
|
||||
private boolean noValue;
|
||||
|
||||
private boolean singleValue;
|
||||
|
||||
private boolean betweenValue;
|
||||
|
||||
private boolean listValue;
|
||||
|
||||
private String typeHandler;
|
||||
|
||||
public String getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Object getSecondValue() {
|
||||
return secondValue;
|
||||
}
|
||||
|
||||
public boolean isNoValue() {
|
||||
return noValue;
|
||||
}
|
||||
|
||||
public boolean isSingleValue() {
|
||||
return singleValue;
|
||||
}
|
||||
|
||||
public boolean isBetweenValue() {
|
||||
return betweenValue;
|
||||
}
|
||||
|
||||
public boolean isListValue() {
|
||||
return listValue;
|
||||
}
|
||||
|
||||
public String getTypeHandler() {
|
||||
return typeHandler;
|
||||
}
|
||||
|
||||
protected Criterion(String condition) {
|
||||
super();
|
||||
this.condition = condition;
|
||||
this.typeHandler = null;
|
||||
this.noValue = true;
|
||||
}
|
||||
|
||||
protected Criterion(String condition, Object value, String typeHandler) {
|
||||
super();
|
||||
this.condition = condition;
|
||||
this.value = value;
|
||||
this.typeHandler = typeHandler;
|
||||
if (value instanceof List<?>) {
|
||||
this.listValue = true;
|
||||
} else {
|
||||
this.singleValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected Criterion(String condition, Object value) {
|
||||
this(condition, value, null);
|
||||
}
|
||||
|
||||
protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
|
||||
super();
|
||||
this.condition = condition;
|
||||
this.value = value;
|
||||
this.secondValue = secondValue;
|
||||
this.typeHandler = typeHandler;
|
||||
this.betweenValue = true;
|
||||
}
|
||||
|
||||
protected Criterion(String condition, Object value, Object secondValue) {
|
||||
this(condition, value, secondValue, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.interceptor;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AuthenticationIgnore {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.interceptor;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
|
||||
import com.tencent.supersonic.auth.api.authentication.constant.UserConstants;
|
||||
import com.tencent.supersonic.auth.authentication.application.UserServiceImpl;
|
||||
import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils;
|
||||
import com.tencent.supersonic.common.util.context.S2ThreadContext;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.catalina.connector.RequestFacade;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.apache.tomcat.util.http.MimeHeaders;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
|
||||
public abstract class AuthenticationInterceptor implements HandlerInterceptor {
|
||||
|
||||
|
||||
protected AuthenticationConfig authenticationConfig;
|
||||
|
||||
protected UserServiceImpl userServiceImpl;
|
||||
|
||||
protected UserTokenUtils userTokenUtils;
|
||||
|
||||
|
||||
protected S2ThreadContext s2ThreadContext;
|
||||
|
||||
|
||||
protected boolean isExcludedUri(String uri) {
|
||||
String excludePathStr = authenticationConfig.getExcludePath();
|
||||
if (Strings.isEmpty(excludePathStr)) {
|
||||
return false;
|
||||
}
|
||||
List<String> excludePaths = Arrays.asList(excludePathStr.split(","));
|
||||
if (CollectionUtils.isEmpty(excludePaths)) {
|
||||
return false;
|
||||
}
|
||||
return excludePaths.stream().anyMatch(uri::startsWith);
|
||||
}
|
||||
|
||||
|
||||
protected boolean isInternalRequest(HttpServletRequest request) {
|
||||
String internal = request.getHeader(UserConstants.INTERNAL);
|
||||
return "true".equalsIgnoreCase(internal);
|
||||
}
|
||||
|
||||
|
||||
protected void reflectSetparam(HttpServletRequest request, String key, String value) {
|
||||
try {
|
||||
if (request instanceof StandardMultipartHttpServletRequest) {
|
||||
RequestFacade servletRequest =
|
||||
(RequestFacade) ((StandardMultipartHttpServletRequest) request).getRequest();
|
||||
Class<? extends HttpServletRequest> servletRequestClazz = servletRequest.getClass();
|
||||
Field request1 = servletRequestClazz.getDeclaredField("request");
|
||||
request1.setAccessible(true);
|
||||
Object o = request1.get(servletRequest);
|
||||
Field coyoteRequest = o.getClass().getDeclaredField("coyoteRequest");
|
||||
coyoteRequest.setAccessible(true);
|
||||
Object o1 = coyoteRequest.get(o);
|
||||
Field headers = o1.getClass().getDeclaredField("headers");
|
||||
headers.setAccessible(true);
|
||||
MimeHeaders o2 = (MimeHeaders) headers.get(o1);
|
||||
if (o2.getValue(key) != null) {
|
||||
o2.setValue(key).setString(value);
|
||||
} else {
|
||||
o2.addValue(key).setString(value);
|
||||
}
|
||||
} else {
|
||||
Class<? extends HttpServletRequest> requestClass = request.getClass();
|
||||
Field request1 = requestClass.getDeclaredField("request");
|
||||
request1.setAccessible(true);
|
||||
Object o = request1.get(request);
|
||||
Field coyoteRequest = o.getClass().getDeclaredField("coyoteRequest");
|
||||
coyoteRequest.setAccessible(true);
|
||||
Object o1 = coyoteRequest.get(o);
|
||||
Field headers = o1.getClass().getDeclaredField("headers");
|
||||
headers.setAccessible(true);
|
||||
MimeHeaders o2 = (MimeHeaders) headers.get(o1);
|
||||
o2.addValue(key).setString(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.interceptor;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
|
||||
import com.tencent.supersonic.auth.authentication.application.UserServiceImpl;
|
||||
import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils;
|
||||
import com.tencent.supersonic.common.exception.AccessException;
|
||||
import com.tencent.supersonic.common.util.context.ContextUtils;
|
||||
import com.tencent.supersonic.common.util.context.S2ThreadContext;
|
||||
import com.tencent.supersonic.common.util.context.ThreadContext;
|
||||
import java.lang.reflect.Method;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor {
|
||||
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws AccessException {
|
||||
authenticationConfig = ContextUtils.getBean(AuthenticationConfig.class);
|
||||
userServiceImpl = ContextUtils.getBean(UserServiceImpl.class);
|
||||
userTokenUtils = ContextUtils.getBean(UserTokenUtils.class);
|
||||
s2ThreadContext = ContextUtils.getBean(S2ThreadContext.class);
|
||||
if (!authenticationConfig.isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
if (isInternalRequest(request)) {
|
||||
String token = userTokenUtils.generateAdminToken();
|
||||
reflectSetparam(request, authenticationConfig.getTokenHttpHeaderKey(), token);
|
||||
}
|
||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||
Method method = handlerMethod.getMethod();
|
||||
AuthenticationIgnore ignore = method.getAnnotation(AuthenticationIgnore.class);
|
||||
if (ignore != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String uri = request.getServletPath();
|
||||
if (isExcludedUri(uri)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
UserWithPassword user = userTokenUtils.getUserWithPassword(request);
|
||||
if (StringUtils.isNotBlank(user.getName())) {
|
||||
ThreadContext threadContext = ThreadContext.builder()
|
||||
.token(request.getHeader(authenticationConfig.getTokenHttpHeaderKey()))
|
||||
.userName(user.getName())
|
||||
.build();
|
||||
s2ThreadContext.set(threadContext);
|
||||
return true;
|
||||
}
|
||||
throw new AccessException("authentication failed, please login");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.interceptor;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
public class InterceptorFactory implements WebMvcConfigurer {
|
||||
|
||||
|
||||
private List<AuthenticationInterceptor> authenticationInterceptors;
|
||||
|
||||
public InterceptorFactory() {
|
||||
authenticationInterceptors = SpringFactoriesLoader.loadFactories(AuthenticationInterceptor.class,
|
||||
Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
for (AuthenticationInterceptor authenticationInterceptor : authenticationInterceptors) {
|
||||
registry.addInterceptor(authenticationInterceptor).addPathPatterns("/**")
|
||||
.excludePathPatterns("/", "/webapp/**","/error");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.repository;
|
||||
|
||||
import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO;
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository {
|
||||
|
||||
List<UserDO> getUserList();
|
||||
|
||||
void addUser(UserDO userDO);
|
||||
|
||||
UserDO getUser(String name);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.strategy;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.service.UserStrategy;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class FakeUserStrategy implements UserStrategy {
|
||||
|
||||
@Override
|
||||
public boolean accept(boolean isEnableAuthentication) {
|
||||
return !isEnableAuthentication;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User findUser(HttpServletRequest request, HttpServletResponse response) {
|
||||
return User.getFakeUser();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.strategy;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.service.UserStrategy;
|
||||
import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
@Service
|
||||
public class HttpHeaderUserStrategy implements UserStrategy {
|
||||
|
||||
|
||||
private final UserTokenUtils userTokenUtils;
|
||||
|
||||
|
||||
public HttpHeaderUserStrategy(UserTokenUtils userTokenUtils) {
|
||||
this.userTokenUtils = userTokenUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(boolean isEnableAuthentication) {
|
||||
return isEnableAuthentication;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User findUser(HttpServletRequest request, HttpServletResponse response) {
|
||||
return userTokenUtils.getUser(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.strategy;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
|
||||
import com.tencent.supersonic.auth.api.authentication.service.UserStrategy;
|
||||
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
|
||||
import java.util.List;
|
||||
import javax.annotation.PostConstruct;
|
||||
import lombok.Data;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
@Configuration
|
||||
@Data
|
||||
public class UserStrategyFactory {
|
||||
|
||||
|
||||
private List<UserStrategy> userStrategyList;
|
||||
|
||||
|
||||
private AuthenticationConfig authenticationConfig;
|
||||
|
||||
public UserStrategyFactory(AuthenticationConfig authenticationConfig, List<UserStrategy> userStrategyList) {
|
||||
this.authenticationConfig = authenticationConfig;
|
||||
this.userStrategyList = userStrategyList;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void setUserStrategy() {
|
||||
for (UserStrategy userStrategy : userStrategyList) {
|
||||
if (userStrategy.accept(authenticationConfig.isEnabled())) {
|
||||
UserHolder.setStrategy(userStrategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.tencent.supersonic.auth.authentication.domain.utils;
|
||||
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_ALGORITHM;
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_CREATE_TIME;
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_PREFIX;
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_TIME_OUT;
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_DISPLAY_NAME;
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_EMAIL;
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_ID;
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_NAME;
|
||||
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_PASSWORD;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
|
||||
import com.tencent.supersonic.common.exception.AccessException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserTokenUtils {
|
||||
|
||||
private AuthenticationConfig authenticationConfig;
|
||||
|
||||
public UserTokenUtils(AuthenticationConfig authenticationConfig) {
|
||||
this.authenticationConfig = authenticationConfig;
|
||||
}
|
||||
|
||||
public String generateToken(UserWithPassword user) {
|
||||
Map<String, Object> claims = new HashMap<>(5);
|
||||
claims.put(TOKEN_USER_ID, user.getId());
|
||||
claims.put(TOKEN_USER_NAME, StringUtils.isEmpty(user.getName()) ? "" : user.getName());
|
||||
claims.put(TOKEN_USER_PASSWORD, StringUtils.isEmpty(user.getPassword()) ? "" : user.getPassword());
|
||||
claims.put(TOKEN_USER_DISPLAY_NAME, user.getDisplayName());
|
||||
claims.put(TOKEN_CREATE_TIME, System.currentTimeMillis());
|
||||
return generate(claims);
|
||||
}
|
||||
|
||||
public String generateAdminToken() {
|
||||
Map<String, Object> claims = new HashMap<>(5);
|
||||
claims.put(TOKEN_USER_ID, "1");
|
||||
claims.put(TOKEN_USER_NAME, "admin");
|
||||
claims.put(TOKEN_USER_PASSWORD, "admin");
|
||||
claims.put(TOKEN_USER_DISPLAY_NAME, "admin");
|
||||
claims.put(TOKEN_CREATE_TIME, System.currentTimeMillis());
|
||||
return generate(claims);
|
||||
}
|
||||
|
||||
|
||||
public User getUser(HttpServletRequest request) {
|
||||
String token = request.getHeader(authenticationConfig.getTokenHttpHeaderKey());
|
||||
final Claims claims = getClaims(token);
|
||||
Long userId = Long.parseLong(claims.getOrDefault(TOKEN_USER_ID, 0).toString());
|
||||
String userName = String.valueOf(claims.get(TOKEN_USER_NAME));
|
||||
String email = String.valueOf(claims.get(TOKEN_USER_EMAIL));
|
||||
String displayName = String.valueOf(claims.get(TOKEN_USER_DISPLAY_NAME));
|
||||
return User.get(userId, userName, displayName, email);
|
||||
}
|
||||
|
||||
public UserWithPassword getUserWithPassword(HttpServletRequest request) {
|
||||
String token = request.getHeader(authenticationConfig.getTokenHttpHeaderKey());
|
||||
if (StringUtils.isBlank(token)) {
|
||||
throw new AccessException("token is blank, get user failed");
|
||||
}
|
||||
final Claims claims = getClaims(token);
|
||||
Long userId = Long.parseLong(claims.getOrDefault(TOKEN_USER_ID, 0).toString());
|
||||
String userName = String.valueOf(claims.get(TOKEN_USER_NAME));
|
||||
String email = String.valueOf(claims.get(TOKEN_USER_EMAIL));
|
||||
String displayName = String.valueOf(claims.get(TOKEN_USER_DISPLAY_NAME));
|
||||
String password = String.valueOf(claims.get(TOKEN_USER_PASSWORD));
|
||||
return UserWithPassword.get(userId, userName, displayName, email, password);
|
||||
}
|
||||
|
||||
private Claims getClaims(String token) {
|
||||
Claims claims;
|
||||
try {
|
||||
claims = Jwts.parser()
|
||||
.setSigningKey(authenticationConfig.getTokenSecret().getBytes(StandardCharsets.UTF_8))
|
||||
.parseClaimsJws(token.startsWith(TOKEN_PREFIX)
|
||||
? token.substring(token.indexOf(TOKEN_PREFIX) + TOKEN_PREFIX.length()).trim() :
|
||||
token.trim()).getBody();
|
||||
} catch (Exception e) {
|
||||
throw new AccessException("parse user info from token failed :" + token);
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
private String generate(Map<String, Object> claims) {
|
||||
return toTokenString(claims);
|
||||
}
|
||||
|
||||
private String toTokenString(Map<String, Object> claims) {
|
||||
long expiration = Long.parseLong(claims.get(TOKEN_CREATE_TIME) + "") + TOKEN_TIME_OUT;
|
||||
|
||||
SignatureAlgorithm.valueOf(TOKEN_ALGORITHM);
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(claims.get(TOKEN_USER_NAME).toString())
|
||||
.setExpiration(new Date(expiration))
|
||||
.signWith(SignatureAlgorithm.valueOf(TOKEN_ALGORITHM),
|
||||
authenticationConfig.getTokenSecret().getBytes(StandardCharsets.UTF_8))
|
||||
.compact();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.tencent.supersonic.auth.authentication.infrastructure.mapper;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO;
|
||||
import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDOExample;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface UserDOMapper {
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
long countByExample(UserDOExample example);
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
int deleteByPrimaryKey(Long id);
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
int insert(UserDO record);
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
int insertSelective(UserDO record);
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
List<UserDO> selectByExample(UserDOExample example);
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
UserDO selectByPrimaryKey(Long id);
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
int updateByPrimaryKeySelective(UserDO record);
|
||||
|
||||
/**
|
||||
* @mbg.generated
|
||||
*/
|
||||
int updateByPrimaryKey(UserDO record);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.tencent.supersonic.auth.authentication.infrastructure.repository;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO;
|
||||
import com.tencent.supersonic.auth.authentication.domain.dataobject.UserDOExample;
|
||||
import com.tencent.supersonic.auth.authentication.domain.repository.UserRepository;
|
||||
import com.tencent.supersonic.auth.authentication.infrastructure.mapper.UserDOMapper;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserRepositoryImpl implements UserRepository {
|
||||
|
||||
|
||||
private UserDOMapper userDOMapper;
|
||||
|
||||
|
||||
public UserRepositoryImpl(UserDOMapper userDOMapper) {
|
||||
this.userDOMapper = userDOMapper;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<UserDO> getUserList() {
|
||||
return userDOMapper.selectByExample(new UserDOExample());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUser(UserDO userDO) {
|
||||
userDOMapper.insert(userDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDO getUser(String name) {
|
||||
UserDOExample userDOExample = new UserDOExample();
|
||||
userDOExample.createCriteria().andNameEqualTo(name);
|
||||
List<UserDO> userDOS = userDOMapper.selectByExample(userDOExample);
|
||||
Optional<UserDO> userDOOptional = userDOS.stream().findFirst();
|
||||
return userDOOptional.orElse(null);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.tencent.supersonic.auth.authentication.rest;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
|
||||
import com.tencent.supersonic.auth.api.authentication.service.UserService;
|
||||
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth/user")
|
||||
@Slf4j
|
||||
public class UserController {
|
||||
|
||||
private UserService userService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/getCurrentUser")
|
||||
public User getCurrentUser(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
|
||||
return UserHolder.findUser(httpServletRequest, httpServletResponse);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/getUserNames")
|
||||
public List<String> getUserNames() {
|
||||
return userService.getUserNames();
|
||||
}
|
||||
|
||||
@GetMapping("/getUserList")
|
||||
public List<User> getUserList() {
|
||||
return userService.getUserList();
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public void register(@RequestBody UserReq userCmd) {
|
||||
userService.register(userCmd);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public String login(@RequestBody UserReq userCmd) {
|
||||
return userService.login(userCmd);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
145
auth/authentication/src/main/resources/mapper/UserDOMapper.xml
Normal file
145
auth/authentication/src/main/resources/mapper/UserDOMapper.xml
Normal file
@@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.tencent.supersonic.auth.authentication.infrastructure.mapper.UserDOMapper">
|
||||
<resultMap id="BaseResultMap" type="com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO">
|
||||
<id column="id" jdbcType="BIGINT" property="id" />
|
||||
<result column="name" jdbcType="VARCHAR" property="name" />
|
||||
<result column="password" jdbcType="VARCHAR" property="password" />
|
||||
<result column="display_name" jdbcType="VARCHAR" property="displayName" />
|
||||
<result column="email" jdbcType="VARCHAR" property="email" />
|
||||
</resultMap>
|
||||
<sql id="Example_Where_Clause">
|
||||
<where>
|
||||
<foreach collection="oredCriteria" item="criteria" separator="or">
|
||||
<if test="criteria.valid">
|
||||
<trim prefix="(" prefixOverrides="and" suffix=")">
|
||||
<foreach collection="criteria.criteria" item="criterion">
|
||||
<choose>
|
||||
<when test="criterion.noValue">
|
||||
and ${criterion.condition}
|
||||
</when>
|
||||
<when test="criterion.singleValue">
|
||||
and ${criterion.condition} #{criterion.value}
|
||||
</when>
|
||||
<when test="criterion.betweenValue">
|
||||
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
|
||||
</when>
|
||||
<when test="criterion.listValue">
|
||||
and ${criterion.condition}
|
||||
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
|
||||
#{listItem}
|
||||
</foreach>
|
||||
</when>
|
||||
</choose>
|
||||
</foreach>
|
||||
</trim>
|
||||
</if>
|
||||
</foreach>
|
||||
</where>
|
||||
</sql>
|
||||
<sql id="Base_Column_List">
|
||||
id, name, password, display_name, email
|
||||
</sql>
|
||||
<select id="selectByExample" parameterType="com.tencent.supersonic.auth.authentication.domain.dataobject.UserDOExample" resultMap="BaseResultMap">
|
||||
select
|
||||
<if test="distinct">
|
||||
distinct
|
||||
</if>
|
||||
<include refid="Base_Column_List" />
|
||||
from s2_user
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
<if test="orderByClause != null">
|
||||
order by ${orderByClause}
|
||||
</if>
|
||||
<if test="limitStart != null and limitStart>=0">
|
||||
limit #{limitStart} , #{limitEnd}
|
||||
</if>
|
||||
</select>
|
||||
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
|
||||
select
|
||||
<include refid="Base_Column_List" />
|
||||
from s2_user
|
||||
where id = #{id,jdbcType=BIGINT}
|
||||
</select>
|
||||
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
|
||||
delete from s2_user
|
||||
where id = #{id,jdbcType=BIGINT}
|
||||
</delete>
|
||||
<insert id="insert" parameterType="com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO">
|
||||
insert into s2_user (id, name, password,
|
||||
display_name, email)
|
||||
values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},
|
||||
#{displayName,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR})
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO">
|
||||
insert into s2_user
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
id,
|
||||
</if>
|
||||
<if test="name != null">
|
||||
name,
|
||||
</if>
|
||||
<if test="password != null">
|
||||
password,
|
||||
</if>
|
||||
<if test="displayName != null">
|
||||
display_name,
|
||||
</if>
|
||||
<if test="email != null">
|
||||
email,
|
||||
</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
#{id,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="name != null">
|
||||
#{name,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="password != null">
|
||||
#{password,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="displayName != null">
|
||||
#{displayName,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="email != null">
|
||||
#{email,jdbcType=VARCHAR},
|
||||
</if>
|
||||
</trim>
|
||||
</insert>
|
||||
<select id="countByExample" parameterType="com.tencent.supersonic.auth.authentication.domain.dataobject.UserDOExample" resultType="java.lang.Long">
|
||||
select count(*) from s2_user
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
</select>
|
||||
<update id="updateByPrimaryKeySelective" parameterType="com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO">
|
||||
update s2_user
|
||||
<set>
|
||||
<if test="name != null">
|
||||
name = #{name,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="password != null">
|
||||
password = #{password,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="displayName != null">
|
||||
display_name = #{displayName,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="email != null">
|
||||
email = #{email,jdbcType=VARCHAR},
|
||||
</if>
|
||||
</set>
|
||||
where id = #{id,jdbcType=BIGINT}
|
||||
</update>
|
||||
<update id="updateByPrimaryKey" parameterType="com.tencent.supersonic.auth.authentication.domain.dataobject.UserDO">
|
||||
update s2_user
|
||||
set name = #{name,jdbcType=VARCHAR},
|
||||
password = #{password,jdbcType=VARCHAR},
|
||||
display_name = #{displayName,jdbcType=VARCHAR},
|
||||
email = #{email,jdbcType=VARCHAR}
|
||||
where id = #{id,jdbcType=BIGINT}
|
||||
</update>
|
||||
</mapper>
|
||||
51
auth/authorization/pom.xml
Normal file
51
auth/authorization/pom.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>auth</artifactId>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>auth-authorization</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>auth-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>auth-authentication</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.tencent.supersonic.auth.authorization.application;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gson.Gson;
|
||||
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes;
|
||||
import com.tencent.supersonic.auth.api.authorization.pojo.AuthResGrp;
|
||||
import com.tencent.supersonic.auth.api.authorization.pojo.DimensionFilter;
|
||||
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
|
||||
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
|
||||
import com.tencent.supersonic.auth.authorization.domain.pojo.AuthGroup;
|
||||
import com.tencent.supersonic.auth.authorization.domain.pojo.AuthRule;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AuthApplicationService {
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
private List<AuthGroup> load() {
|
||||
List<String> rows = jdbcTemplate.queryForList("select config from s2_auth_groups", String.class);
|
||||
Gson g = new Gson();
|
||||
return rows.stream().map(row -> g.fromJson(row, AuthGroup.class)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<AuthGroup> queryAuthGroups(String domainId, Integer groupId) {
|
||||
return load().stream()
|
||||
.filter(group -> (Objects.isNull(groupId) || groupId.equals(group.getGroupId()))
|
||||
&& domainId.equals(group.getDomainId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void updateAuthGroup(AuthGroup group) {
|
||||
Gson g = new Gson();
|
||||
if (group.getGroupId() == null) {
|
||||
int nextGroupId = 1;
|
||||
String sql = "select max(group_id) as group_id from s2_auth_groups";
|
||||
Integer obj = jdbcTemplate.queryForObject(sql, Integer.class);
|
||||
if (obj != null) {
|
||||
nextGroupId = obj + 1;
|
||||
}
|
||||
group.setGroupId(nextGroupId);
|
||||
jdbcTemplate.update("insert into s2_auth_groups (group_id, config) values (?, ?);", nextGroupId,
|
||||
g.toJson(group));
|
||||
} else {
|
||||
jdbcTemplate.update("update s2_auth_groups set config = ? where group_id = ?;", g.toJson(group),
|
||||
group.getGroupId());
|
||||
}
|
||||
}
|
||||
|
||||
public AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, HttpServletRequest request) {
|
||||
List<AuthGroup> groups = load().stream().
|
||||
filter(group -> group.getAuthorizedUsers().contains(req.getUser()) && req.getDomainId()
|
||||
.equals(group.getDomainId())).
|
||||
collect(Collectors.toList());
|
||||
AuthorizedResourceResp resource = new AuthorizedResourceResp();
|
||||
Map<String, List<AuthGroup>> authGroupsByDomainId = groups.stream()
|
||||
.collect(Collectors.groupingBy(AuthGroup::getDomainId));
|
||||
Map<String, List<AuthRes>> reqAuthRes = req.getResources().stream()
|
||||
.collect(Collectors.groupingBy(AuthRes::getDomainId));
|
||||
|
||||
for (String domainId : reqAuthRes.keySet()) {
|
||||
List<AuthRes> reqResourcesList = reqAuthRes.get(domainId);
|
||||
AuthResGrp rg = new AuthResGrp();
|
||||
if (authGroupsByDomainId.containsKey(domainId)) {
|
||||
List<AuthGroup> authGroups = authGroupsByDomainId.get(domainId);
|
||||
for (AuthRes reqRes : reqResourcesList) {
|
||||
for (AuthGroup authRuleGroup : authGroups) {
|
||||
List<AuthRule> authRules = authRuleGroup.getAuthRules();
|
||||
List<String> allAuthItems = new ArrayList<>();
|
||||
authRules.stream().forEach(authRule -> allAuthItems.addAll(authRule.resourceNames()));
|
||||
|
||||
if (allAuthItems.contains(reqRes.getName())) {
|
||||
rg.getGroup().add(reqRes);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Objects.nonNull(rg) && !CollectionUtils.isEmpty(rg.getGroup())) {
|
||||
resource.getResources().add(rg);
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(req.getDomainId())) {
|
||||
List<AuthGroup> authGroups = authGroupsByDomainId.get(req.getDomainId());
|
||||
if (!CollectionUtils.isEmpty(authGroups)) {
|
||||
for (AuthGroup group : authGroups) {
|
||||
if (group.getDimensionFilters() != null
|
||||
&& group.getDimensionFilters().stream().anyMatch(expr -> !Strings.isNullOrEmpty(expr))) {
|
||||
DimensionFilter df = new DimensionFilter();
|
||||
df.setDescription(group.getDimensionFilterDescription());
|
||||
df.setExpressions(group.getDimensionFilters());
|
||||
resource.getFilters().add(df);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void removeAuthGroup(AuthGroup group) {
|
||||
jdbcTemplate.update("delete from s2_auth_groups where group_id = ?", group.getGroupId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.tencent.supersonic.auth.authorization.application;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
|
||||
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
|
||||
import com.tencent.supersonic.auth.api.authorization.service.AuthService;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final AuthApplicationService authApplicationService;
|
||||
|
||||
public AuthServiceImpl(AuthApplicationService authApplicationService) {
|
||||
this.authApplicationService = authApplicationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizedResourceResp queryAuthorizedResources(HttpServletRequest request, QueryAuthResReq req) {
|
||||
return authApplicationService.queryAuthorizedResources(req, request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.tencent.supersonic.auth.authorization.domain.pojo;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AuthGroup {
|
||||
|
||||
private String domainId;
|
||||
private String name;
|
||||
private Integer groupId;
|
||||
private List<AuthRule> authRules;
|
||||
/**
|
||||
* row permission expression
|
||||
*/
|
||||
private List<String> dimensionFilters;
|
||||
/**
|
||||
* row permission expression description information
|
||||
*/
|
||||
private String dimensionFilterDescription;
|
||||
|
||||
private List<String> authorizedUsers;
|
||||
/**
|
||||
* authorization Department Id
|
||||
*/
|
||||
private List<String> authorizedDepartmentIds;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.tencent.supersonic.auth.authorization.domain.pojo;
|
||||
|
||||
import java.beans.Transient;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AuthRule {
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
private List<String> metrics;
|
||||
private List<String> dimensions;
|
||||
|
||||
@Transient
|
||||
public List<String> resourceNames() {
|
||||
ArrayList<String> res = new ArrayList<>();
|
||||
if (metrics != null) {
|
||||
res.addAll(metrics);
|
||||
}
|
||||
|
||||
if (dimensions != null) {
|
||||
res.addAll(dimensions);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.tencent.supersonic.auth.authorization.rest;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
|
||||
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
|
||||
import com.tencent.supersonic.auth.authorization.application.AuthApplicationService;
|
||||
import com.tencent.supersonic.auth.authorization.domain.pojo.AuthGroup;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@Slf4j
|
||||
public class AuthController {
|
||||
|
||||
private final AuthApplicationService service;
|
||||
|
||||
public AuthController(AuthApplicationService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@GetMapping("/queryGroup")
|
||||
public List<AuthGroup> queryAuthGroup(@RequestParam("domainId") String domainId,
|
||||
@RequestParam(value = "groupId", required = false) Integer groupId) {
|
||||
return service.queryAuthGroups(domainId, groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建权限组
|
||||
*/
|
||||
@PostMapping("/createGroup")
|
||||
public void newAuthGroup(@RequestBody AuthGroup group) {
|
||||
group.setGroupId(null);
|
||||
service.updateAuthGroup(group);
|
||||
}
|
||||
|
||||
@PostMapping("/removeGroup")
|
||||
public void removeAuthGroup(@RequestBody AuthGroup group) {
|
||||
service.removeAuthGroup(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新权限组
|
||||
*
|
||||
* @param group
|
||||
*/
|
||||
@PostMapping("/updateGroup")
|
||||
public void updateAuthGroup(@RequestBody AuthGroup group) {
|
||||
if (group.getGroupId() == null || group.getGroupId() == 0) {
|
||||
throw new RuntimeException("groupId is empty");
|
||||
}
|
||||
service.updateAuthGroup(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询有权限访问的受限资源id
|
||||
*
|
||||
* @param req
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/queryAuthorizedRes")
|
||||
public AuthorizedResourceResp queryAuthorizedResources(@RequestBody QueryAuthResReq req,
|
||||
HttpServletRequest request) {
|
||||
return service.queryAuthorizedResources(req, request);
|
||||
}
|
||||
}
|
||||
21
auth/pom.xml
Normal file
21
auth/pom.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>supersonic</artifactId>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>auth</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>authentication</module>
|
||||
<module>authorization</module>
|
||||
</modules>
|
||||
|
||||
|
||||
</project>
|
||||
33
chat/api/pom.xml
Normal file
33
chat/api/pom.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>chat</artifactId>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>chat-api</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>auth-api</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>semantic-api</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChatContext {
|
||||
|
||||
private Integer chatId;
|
||||
private String queryText;
|
||||
private SemanticParseInfo parseInfo = new SemanticParseInfo();
|
||||
private String user;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DataInfo {
|
||||
|
||||
private Integer itemId;
|
||||
private String name;
|
||||
private String bizName;
|
||||
private String value;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DomainInfo extends DataInfo implements Serializable {
|
||||
|
||||
private List<String> words;
|
||||
private String primaryEntityBizName;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class EntityInfo {
|
||||
|
||||
private DomainInfo domainInfo = new DomainInfo();
|
||||
private List<DataInfo> dimensions = new ArrayList<>();
|
||||
private List<DataInfo> metrics = new ArrayList<>();
|
||||
private String entityId;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class Filter {
|
||||
|
||||
private String bizName;
|
||||
|
||||
private String name;
|
||||
|
||||
private FilterOperatorEnum operator = FilterOperatorEnum.EQUALS;
|
||||
|
||||
private Object value;
|
||||
|
||||
private Long elementID;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SchemaElementCount {
|
||||
|
||||
private Integer count = 0;
|
||||
private double maxSimilarity;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class SchemaElementMatch {
|
||||
|
||||
SchemaElementType elementType;
|
||||
int elementID;
|
||||
double similarity;
|
||||
String word;
|
||||
|
||||
public SchemaElementMatch() {
|
||||
}
|
||||
|
||||
public SchemaElementMatch(SchemaElementType schemaElementType, int elementID, double similarity, String word) {
|
||||
this.elementID = elementID;
|
||||
this.elementType = schemaElementType;
|
||||
this.similarity = similarity;
|
||||
this.word = word;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
public enum SchemaElementType {
|
||||
DOMAIN,
|
||||
METRIC,
|
||||
DIMENSION,
|
||||
VALUE,
|
||||
ENTITY,
|
||||
ID,
|
||||
DATE
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class SchemaMapInfo {
|
||||
|
||||
private Map<Integer, List<SchemaElementMatch>> domainElementMatches = new HashMap<>();
|
||||
|
||||
public Set<Integer> getMatchedDomains() {
|
||||
return domainElementMatches.keySet();
|
||||
}
|
||||
|
||||
public List<SchemaElementMatch> getMatchedElements(Integer domain) {
|
||||
return domainElementMatches.get(domain);
|
||||
}
|
||||
|
||||
public Map<Integer, List<SchemaElementMatch>> getDomainElementMatches() {
|
||||
return domainElementMatches;
|
||||
}
|
||||
|
||||
public void setMatchedElements(Integer domain, List<SchemaElementMatch> elementMatches) {
|
||||
domainElementMatches.put(domain, elementMatches);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.tencent.supersonic.chat.api.pojo;
|
||||
|
||||
|
||||
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
|
||||
import com.tencent.supersonic.common.pojo.DateConf;
|
||||
import com.tencent.supersonic.common.pojo.Order;
|
||||
import com.tencent.supersonic.common.pojo.SchemaItem;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SemanticParseInfo {
|
||||
|
||||
String queryMode;
|
||||
AggregateTypeEnum aggType = AggregateTypeEnum.NONE;
|
||||
Long domainId = 0L;
|
||||
String domainName;
|
||||
Long entity = 0L;
|
||||
List<SchemaItem> metrics = new ArrayList<>();
|
||||
List<SchemaItem> dimensions = new ArrayList<>();
|
||||
List<Filter> dimensionFilters = new ArrayList<>();
|
||||
List<Filter> metricFilters = new ArrayList<>();
|
||||
private List<Order> orders = new ArrayList<>();
|
||||
private DateConf dateInfo;
|
||||
private Long limit;
|
||||
private Boolean nativeQuery = false;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.tencent.supersonic.chat.api.request;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryContextReq {
|
||||
|
||||
private String queryText;
|
||||
private Integer chatId;
|
||||
private Integer domainId = 0;
|
||||
private User user;
|
||||
private SemanticParseInfo parseInfo = new SemanticParseInfo();
|
||||
private SchemaMapInfo mapInfo = new SchemaMapInfo();
|
||||
private boolean saveAnswer = true;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.tencent.supersonic.chat.api.response;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.EntityInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.semantic.api.core.pojo.QueryAuthorization;
|
||||
import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryResultResp {
|
||||
|
||||
private Long queryId;
|
||||
private String queryMode;
|
||||
private String querySql;
|
||||
private int queryState;
|
||||
private List<QueryColumn> queryColumns;
|
||||
private QueryAuthorization queryAuthorization;
|
||||
public EntityInfo entityInfo;
|
||||
private SemanticParseInfo chatContext;
|
||||
private Object response;
|
||||
private List<Map<String, Object>> queryResults;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.tencent.supersonic.chat.api.service;
|
||||
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
|
||||
/**
|
||||
* This interface defines the contract for a schema mapper that identifies references to schema
|
||||
* elements in natural language queries.
|
||||
*
|
||||
* The schema mapper matches queries against the knowledge base which is constructed using the
|
||||
* schema of semantic models.
|
||||
*/
|
||||
public interface SchemaMapper {
|
||||
|
||||
void map(QueryContextReq searchCtx);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.tencent.supersonic.chat.api.service;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This interface defines the contract for a semantic layer that provides a simplified and
|
||||
* consistent view of data from multiple sources.
|
||||
* The semantic layer abstracts away the complexity of the underlying data sources and provides
|
||||
* a unified view of the data that is easier to understand and use.
|
||||
* <p>
|
||||
* The interface defines methods for getting metadata as well as querying data in the semantic layer.
|
||||
* Implementations of this interface should provide concrete implementations that interact with the
|
||||
* underlying data sources and return results in a consistent format. Or it can be implemented
|
||||
* as proxy to a remote semantic service.
|
||||
* </p>
|
||||
*/
|
||||
public interface SemanticLayer {
|
||||
|
||||
QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructReq, User user);
|
||||
|
||||
DomainSchemaResp getDomainSchemaInfo(Long domain);
|
||||
|
||||
List<DomainSchemaResp> getDomainSchemaInfo(List<Long> ids);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.tencent.supersonic.chat.api.service;
|
||||
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
|
||||
/**
|
||||
* This interface defines the contract for a semantic parser that can analyze natural language query
|
||||
* and extract meaning from it.
|
||||
*
|
||||
* The semantic parser uses either rule-based or model-based algorithms to identify query intent
|
||||
* and related semantic items described in the query.
|
||||
*/
|
||||
public interface SemanticParser {
|
||||
|
||||
boolean parse(QueryContextReq queryContext, ChatContext chatCtx);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.tencent.supersonic.chat.api.service;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.response.QueryResultResp;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This interface defines the contract for a semantic query that executes specific type of
|
||||
* query based on the results of semantic parsing.
|
||||
*/
|
||||
public interface SemanticQuery extends Serializable {
|
||||
|
||||
String getQueryMode();
|
||||
|
||||
QueryResultResp execute(QueryContextReq queryCtx, ChatContext chatCtx) throws Exception;
|
||||
|
||||
SchemaElementCount match(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx);
|
||||
|
||||
void updateContext(QueryResultResp queryResponse, ChatContext chatCtx, QueryContextReq queryCtx);
|
||||
|
||||
SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx);
|
||||
|
||||
}
|
||||
130
chat/core/pom.xml
Normal file
130
chat/core/pom.xml
Normal file
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>chat</artifactId>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>chat-core</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<version>${org.testng.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>${commons.compress.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>${mybatis-spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>${alibaba.druid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter-test</artifactId>
|
||||
<version>${mybatis.test.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||
<version>${pagehelper.spring.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>${h2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>chat-knowledge</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>semantic-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>semantic-query</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
<artifactId>chat-api</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.tencent.supersonic.chat.application;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.response.QueryResultResp;
|
||||
import com.tencent.supersonic.chat.domain.dataobject.ChatDO;
|
||||
import com.tencent.supersonic.chat.domain.dataobject.QueryDO;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.ChatQueryVO;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.PageQueryInfoReq;
|
||||
import com.tencent.supersonic.chat.domain.repository.ChatContextRepository;
|
||||
import com.tencent.supersonic.chat.domain.repository.ChatQueryRepository;
|
||||
import com.tencent.supersonic.chat.domain.repository.ChatRepository;
|
||||
import com.tencent.supersonic.chat.domain.service.ChatService;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service("ChatService")
|
||||
@Primary
|
||||
public class ChatServiceImpl implements ChatService {
|
||||
|
||||
private ChatContextRepository chatContextRepository;
|
||||
private ChatRepository chatRepository;
|
||||
private ChatQueryRepository chatQueryRepository;
|
||||
private final Logger logger = LoggerFactory.getLogger(ChatService.class);
|
||||
|
||||
|
||||
public ChatServiceImpl(ChatContextRepository chatContextRepository, ChatRepository chatRepository,
|
||||
ChatQueryRepository chatQueryRepository) {
|
||||
this.chatContextRepository = chatContextRepository;
|
||||
this.chatRepository = chatRepository;
|
||||
this.chatQueryRepository = chatQueryRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getContextDomain(Integer chatId) {
|
||||
if (Objects.isNull(chatId)) {
|
||||
return null;
|
||||
}
|
||||
ChatContext chatContext = getOrCreateContext(chatId);
|
||||
if (Objects.isNull(chatContext)) {
|
||||
return null;
|
||||
}
|
||||
SemanticParseInfo originalSemanticParse = chatContext.getParseInfo();
|
||||
if (Objects.nonNull(originalSemanticParse) && Objects.nonNull(originalSemanticParse.getDomainId())) {
|
||||
return originalSemanticParse.getDomainId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ChatContext getOrCreateContext(int chatId) {
|
||||
return chatContextRepository.getOrCreateContext(chatId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateContext(ChatContext chatCtx) {
|
||||
logger.debug("save ChatContext {}", chatCtx);
|
||||
chatContextRepository.updateContext(chatCtx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchContext(ChatContext chatCtx) {
|
||||
logger.debug("switchContext ChatContext {}", chatCtx);
|
||||
chatCtx.setParseInfo(new SemanticParseInfo());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Boolean addChat(User user, String chatName) {
|
||||
SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String datetime = tempDate.format(new java.util.Date());
|
||||
ChatDO intelligentConversionDO = new ChatDO();
|
||||
intelligentConversionDO.setChatName(chatName);
|
||||
intelligentConversionDO.setCreator(user.getName());
|
||||
intelligentConversionDO.setCreateTime(datetime);
|
||||
intelligentConversionDO.setIsDelete(0);
|
||||
intelligentConversionDO.setLastTime(datetime);
|
||||
intelligentConversionDO.setLastQuestion("Hello, welcome to using supersonic");
|
||||
intelligentConversionDO.setIsTop(0);
|
||||
return chatRepository.createChat(intelligentConversionDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChatDO> getAll(String userName) {
|
||||
return chatRepository.getAll(userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateChatName(Long chatId, String chatName, String userName) {
|
||||
SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String lastTime = tempDate.format(new java.util.Date());
|
||||
return chatRepository.updateChatName(chatId, chatName, lastTime, userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateFeedback(Integer id, Integer score, String feedback) {
|
||||
QueryDO intelligentQueryDO = new QueryDO();
|
||||
intelligentQueryDO.setId(id);
|
||||
intelligentQueryDO.setScore(score);
|
||||
intelligentQueryDO.setFeedback(feedback);
|
||||
return chatRepository.updateFeedback(intelligentQueryDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateChatIsTop(Long chatId, int isTop) {
|
||||
return chatRepository.updateConversionIsTop(chatId, isTop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteChat(Long chatId, String userName) {
|
||||
return chatRepository.deleteChat(chatId, userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageInfo<ChatQueryVO> queryInfo(PageQueryInfoReq pageQueryInfoCommend, long chatId) {
|
||||
return chatQueryRepository.getChatQuery(pageQueryInfoCommend, chatId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addQuery(QueryResultResp queryResponse, QueryContextReq queryContext, ChatContext chatCtx) {
|
||||
chatQueryRepository.createChatQuery(queryResponse, queryContext, chatCtx);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.tencent.supersonic.chat.application;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticLayer;
|
||||
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfig;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigBase;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigEditReq;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigFilter;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigInfo;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.DefaultMetric;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ItemVisibilityInfo;
|
||||
import com.tencent.supersonic.chat.domain.repository.ChatConfigRepository;
|
||||
import com.tencent.supersonic.chat.domain.service.ConfigService;
|
||||
import com.tencent.supersonic.chat.domain.utils.ChatConfigUtils;
|
||||
import com.tencent.supersonic.common.util.json.JsonUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ConfigServiceImpl implements ConfigService {
|
||||
|
||||
private final ChatConfigRepository chaConfigRepository;
|
||||
private final SemanticLayer semanticLayer;
|
||||
private final ChatConfigUtils chatConfigUtils;
|
||||
|
||||
public ConfigServiceImpl(ChatConfigRepository chaConfigRepository,
|
||||
@Lazy SemanticLayer semanticLayer,
|
||||
ChatConfigUtils chatConfigUtils) {
|
||||
this.chaConfigRepository = chaConfigRepository;
|
||||
this.semanticLayer = semanticLayer;
|
||||
this.chatConfigUtils = chatConfigUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long addConfig(ChatConfigBase configBaseCmd, User user) {
|
||||
log.info("[create domain extend] object:{}", JsonUtil.toString(configBaseCmd, true));
|
||||
duplicateCheck(configBaseCmd.getDomainId());
|
||||
permissionCheckLogic(configBaseCmd.getDomainId(), user.getName());
|
||||
ChatConfig chaConfig = chatConfigUtils.newChatConfig(configBaseCmd, user);
|
||||
chaConfigRepository.createConfig(chaConfig);
|
||||
return chaConfig.getDomainId();
|
||||
}
|
||||
|
||||
private void duplicateCheck(Long domainId) {
|
||||
ChatConfigFilter filter = new ChatConfigFilter();
|
||||
filter.setDomainId(domainId);
|
||||
List<ChatConfigInfo> chaConfigDescList = chaConfigRepository.getChatConfig(filter);
|
||||
if (!CollectionUtils.isEmpty(chaConfigDescList)) {
|
||||
throw new RuntimeException("chat config existed, no need to add repeatedly");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Long editConfig(ChatConfigEditReq configEditCmd, User user) {
|
||||
log.info("[edit domain extend] object:{}", JsonUtil.toString(configEditCmd, true));
|
||||
if (Objects.isNull(configEditCmd) || Objects.isNull(configEditCmd.getId()) && Objects.isNull(
|
||||
configEditCmd.getDomainId())) {
|
||||
throw new RuntimeException("editConfig, id and domainId are not allowed to be empty at the same time");
|
||||
}
|
||||
permissionCheckLogic(configEditCmd.getDomainId(), user.getName());
|
||||
ChatConfig chaConfig = chatConfigUtils.editChaConfig(configEditCmd, user);
|
||||
chaConfigRepository.updateConfig(chaConfig);
|
||||
return configEditCmd.getDomainId();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* domain administrators have the right to modify related configuration information.
|
||||
*/
|
||||
private Boolean permissionCheckLogic(Long domainId, String staffName) {
|
||||
// todo
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChatConfigInfo> search(ChatConfigFilter filter, User user) {
|
||||
log.info("[search domain extend] object:{}", JsonUtil.toString(filter, true));
|
||||
List<ChatConfigInfo> chaConfigDescList = chaConfigRepository.getChatConfig(filter);
|
||||
return chaConfigDescList;
|
||||
}
|
||||
|
||||
|
||||
public ChatConfigInfo fetchConfigByDomainId(Long domainId) {
|
||||
return chaConfigRepository.getConfigByDomainId(domainId);
|
||||
}
|
||||
|
||||
public EntityRichInfo fetchEntityDescByDomainId(Long domainId) {
|
||||
|
||||
ChatConfigInfo chaConfigDesc = chaConfigRepository.getConfigByDomainId(domainId);
|
||||
return fetchEntityDescByConfig(chaConfigDesc);
|
||||
}
|
||||
|
||||
public EntityRichInfo fetchEntityDescByConfig(ChatConfigInfo chatConfigDesc) {
|
||||
Long domainId = chatConfigDesc.getDomainId();
|
||||
EntityRichInfo entityDesc = new EntityRichInfo();
|
||||
if (Objects.isNull(chatConfigDesc) || Objects.isNull(chatConfigDesc.getEntity())) {
|
||||
log.info("domainId:{}, entityDesc info is null", domainId);
|
||||
return entityDesc;
|
||||
}
|
||||
DomainSchemaResp domain = semanticLayer.getDomainSchemaInfo(domainId);
|
||||
|
||||
entityDesc.setDomainId(domain.getId());
|
||||
entityDesc.setDomainBizName(domain.getBizName());
|
||||
entityDesc.setDomainName(domain.getName());
|
||||
entityDesc.setNames(chatConfigDesc.getEntity().getNames());
|
||||
|
||||
entityDesc.setEntityIds(chatConfigUtils.generateDimDesc(chatConfigDesc.getEntity().getEntityIds(), domain));
|
||||
entityDesc.setEntityInternalDetailDesc(
|
||||
chatConfigUtils.generateEntityDetailData(chatConfigDesc.getEntity().getDetailData(), domain));
|
||||
return entityDesc;
|
||||
}
|
||||
|
||||
|
||||
public List<DefaultMetric> fetchDefaultMetricDescByDomainId(Long domainId) {
|
||||
ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId);
|
||||
return fetchDefaultMetricDescByConfig(chatConfigDesc);
|
||||
}
|
||||
|
||||
public List<DefaultMetric> fetchDefaultMetricDescByConfig(ChatConfigInfo chatConfigDesc) {
|
||||
Long domainId = chatConfigDesc.getDomainId();
|
||||
DomainSchemaResp domain = semanticLayer.getDomainSchemaInfo(domainId);
|
||||
List<DefaultMetric> defaultMetricDescList = new ArrayList<>();
|
||||
if (Objects.isNull(chatConfigDesc) || CollectionUtils.isEmpty(chatConfigDesc.getDefaultMetrics())) {
|
||||
log.info("domainId:{}, defaultMetricDescList info is null", domainId);
|
||||
return defaultMetricDescList;
|
||||
}
|
||||
List<Long> metricIds = chatConfigDesc.getDefaultMetrics().stream()
|
||||
.map(defaultMetricInfo -> defaultMetricInfo.getMetricId()).collect(Collectors.toList());
|
||||
Map<Long, MetricSchemaResp> metricIdAndDescPair = chatConfigUtils.generateMetricIdAndDescPair(metricIds,
|
||||
domain);
|
||||
chatConfigDesc.getDefaultMetrics().stream().forEach(defaultMetricInfo -> {
|
||||
DefaultMetric defaultMetricDesc = new DefaultMetric();
|
||||
BeanUtils.copyProperties(defaultMetricInfo, defaultMetricDesc);
|
||||
if (metricIdAndDescPair.containsKey(defaultMetricInfo.getMetricId())) {
|
||||
MetricSchemaResp metricDesc = metricIdAndDescPair.get(defaultMetricInfo.getMetricId());
|
||||
defaultMetricDesc.setBizName(metricDesc.getBizName());
|
||||
defaultMetricDesc.setName(metricDesc.getName());
|
||||
}
|
||||
defaultMetricDescList.add(defaultMetricDesc);
|
||||
});
|
||||
return defaultMetricDescList;
|
||||
}
|
||||
|
||||
public ItemVisibilityInfo fetchVisibilityDescByDomainId(Long domainId) {
|
||||
ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId);
|
||||
return fetchVisibilityDescByConfig(chatConfigDesc);
|
||||
}
|
||||
|
||||
private ItemVisibilityInfo fetchVisibilityDescByConfig(ChatConfigInfo chatConfigDesc) {
|
||||
ItemVisibilityInfo itemVisibilityDesc = new ItemVisibilityInfo();
|
||||
Long domainId = chatConfigDesc.getDomainId();
|
||||
DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId);
|
||||
List<Long> dimIdAllList = chatConfigUtils.generateAllDimIdList(domainSchemaDesc);
|
||||
List<Long> metricIdAllList = chatConfigUtils.generateAllMetricIdList(domainSchemaDesc);
|
||||
|
||||
List<Long> blackDimIdList = new ArrayList<>();
|
||||
List<Long> blackMetricIdList = new ArrayList<>();
|
||||
if (Objects.nonNull(chatConfigDesc.getVisibility())) {
|
||||
if (!CollectionUtils.isEmpty(chatConfigDesc.getVisibility().getBlackDimIdList())) {
|
||||
blackDimIdList.addAll(chatConfigDesc.getVisibility().getBlackDimIdList());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(chatConfigDesc.getVisibility().getBlackMetricIdList())) {
|
||||
blackMetricIdList.addAll(chatConfigDesc.getVisibility().getBlackMetricIdList());
|
||||
}
|
||||
}
|
||||
List<Long> whiteMetricIdList = metricIdAllList.stream().filter(id -> !blackMetricIdList.contains(id))
|
||||
.collect(Collectors.toList());
|
||||
List<Long> whiteDimIdList = dimIdAllList.stream().filter(id -> !blackDimIdList.contains(id))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
itemVisibilityDesc.setBlackDimIdList(blackDimIdList);
|
||||
itemVisibilityDesc.setBlackMetricIdList(blackMetricIdList);
|
||||
itemVisibilityDesc.setWhiteDimIdList(whiteDimIdList);
|
||||
itemVisibilityDesc.setWhiteMetricIdList(whiteMetricIdList);
|
||||
|
||||
return itemVisibilityDesc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatConfigRichInfo getConfigRichInfo(Long domainId) {
|
||||
ChatConfigRichInfo chaConfigRichDesc = new ChatConfigRichInfo();
|
||||
ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId);
|
||||
BeanUtils.copyProperties(chatConfigDesc, chaConfigRichDesc);
|
||||
|
||||
DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId);
|
||||
chaConfigRichDesc.setBizName(domainSchemaDesc.getBizName());
|
||||
chaConfigRichDesc.setName(domainSchemaDesc.getName());
|
||||
|
||||
chaConfigRichDesc.setDefaultMetrics(fetchDefaultMetricDescByConfig(chatConfigDesc));
|
||||
chaConfigRichDesc.setVisibility(fetchVisibilityDescByConfig(chatConfigDesc));
|
||||
chaConfigRichDesc.setEntity(fetchEntityDescByConfig(chatConfigDesc));
|
||||
|
||||
return chaConfigRichDesc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.tencent.supersonic.chat.application;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.DataInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.DomainInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.EntityInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.Filter;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticLayer;
|
||||
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils;
|
||||
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
|
||||
import com.tencent.supersonic.common.pojo.DateConf;
|
||||
import com.tencent.supersonic.common.pojo.SchemaItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class DomainEntityService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DomainEntityService.class);
|
||||
@Autowired
|
||||
private SemanticLayer semanticLayer;
|
||||
|
||||
@Autowired
|
||||
private DefaultSemanticInternalUtils defaultSemanticUtils;
|
||||
|
||||
public EntityInfo getEntityInfo(QueryContextReq queryCtx, ChatContext chatCtx, User user) {
|
||||
SemanticParseInfo parseInfo = queryCtx.getParseInfo();
|
||||
|
||||
if (parseInfo != null && parseInfo.getDomainId() > 0) {
|
||||
EntityInfo entityInfo = getEntityInfo(parseInfo.getDomainId());
|
||||
if (parseInfo.getDimensionFilters().size() <= 0) {
|
||||
entityInfo.setMetrics(null);
|
||||
entityInfo.setDimensions(null);
|
||||
return entityInfo;
|
||||
}
|
||||
if (entityInfo.getDomainInfo() != null && entityInfo.getDomainInfo().getPrimaryEntityBizName() != null) {
|
||||
String domainInfoPrimaryName = entityInfo.getDomainInfo().getPrimaryEntityBizName();
|
||||
String domainInfoId = "";
|
||||
for (Filter chatFilter : parseInfo.getDimensionFilters()) {
|
||||
if (chatFilter.getBizName().equals(domainInfoPrimaryName)) {
|
||||
if (chatFilter.getOperator().equals(FilterOperatorEnum.EQUALS)) {
|
||||
domainInfoId = chatFilter.getValue().toString();
|
||||
}
|
||||
if (chatFilter.getOperator().equals(FilterOperatorEnum.IN)) {
|
||||
domainInfoId = ((List<String>) chatFilter.getValue()).get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!"".equals(domainInfoId)) {
|
||||
try {
|
||||
setMainDomain(entityInfo, parseInfo.getDomainId(),
|
||||
domainInfoId, user);
|
||||
|
||||
return entityInfo;
|
||||
} catch (Exception e) {
|
||||
logger.error("setMaintDomain error {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public EntityInfo getEntityInfo(Long domain) {
|
||||
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domain);
|
||||
return getEntityInfo(chaConfigRichDesc.getEntity());
|
||||
}
|
||||
|
||||
private EntityInfo getEntityInfo(EntityRichInfo entityDesc) {
|
||||
EntityInfo entityInfo = new EntityInfo();
|
||||
|
||||
if (entityDesc != null) {
|
||||
DomainInfo domainInfo = new DomainInfo();
|
||||
domainInfo.setItemId(Integer.valueOf(entityDesc.getDomainId().intValue()));
|
||||
domainInfo.setName(entityDesc.getDomainName());
|
||||
domainInfo.setWords(entityDesc.getNames());
|
||||
domainInfo.setBizName(entityDesc.getDomainBizName());
|
||||
if (entityDesc.getEntityIds().size() > 0) {
|
||||
domainInfo.setPrimaryEntityBizName(entityDesc.getEntityIds().get(0).getBizName());
|
||||
}
|
||||
entityInfo.setDomainInfo(domainInfo);
|
||||
List<DataInfo> dimensions = new ArrayList<>();
|
||||
List<DataInfo> metrics = new ArrayList<>();
|
||||
if (entityDesc.getEntityInternalDetailDesc() != null) {
|
||||
for (DimSchemaResp dimensionDesc : entityDesc.getEntityInternalDetailDesc().getDimensionList()) {
|
||||
DataInfo mainEntityDimension = new DataInfo();
|
||||
mainEntityDimension.setItemId(dimensionDesc.getId().intValue());
|
||||
mainEntityDimension.setName(dimensionDesc.getName());
|
||||
mainEntityDimension.setBizName(dimensionDesc.getBizName());
|
||||
dimensions.add(mainEntityDimension);
|
||||
}
|
||||
entityInfo.setDimensions(dimensions);
|
||||
for (MetricSchemaResp metricDesc : entityDesc.getEntityInternalDetailDesc().getMetricList()) {
|
||||
DataInfo dataInfo = new DataInfo();
|
||||
dataInfo.setName(metricDesc.getName());
|
||||
dataInfo.setBizName(metricDesc.getBizName());
|
||||
dataInfo.setItemId(metricDesc.getId().intValue());
|
||||
metrics.add(dataInfo);
|
||||
}
|
||||
entityInfo.setMetrics(metrics);
|
||||
}
|
||||
}
|
||||
return entityInfo;
|
||||
}
|
||||
|
||||
public void setMainDomain(EntityInfo domainInfo, Long domain, String entity, User user) {
|
||||
domainInfo.setEntityId(entity);
|
||||
SemanticParseInfo semanticParseInfo = new SemanticParseInfo();
|
||||
semanticParseInfo.setDomainId(Long.valueOf(domain));
|
||||
semanticParseInfo.setNativeQuery(true);
|
||||
semanticParseInfo.setMetrics(getMetrics(domainInfo));
|
||||
semanticParseInfo.setDimensions(getDimensions(domainInfo));
|
||||
DateConf dateInfo = new DateConf();
|
||||
dateInfo.setUnit(1);
|
||||
dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS);
|
||||
semanticParseInfo.setDateInfo(dateInfo);
|
||||
|
||||
// add filter
|
||||
Filter chatFilter = new Filter();
|
||||
chatFilter.setValue(String.valueOf(entity));
|
||||
chatFilter.setOperator(FilterOperatorEnum.EQUALS);
|
||||
chatFilter.setBizName(getEntityPrimaryName(domainInfo));
|
||||
List<Filter> chatFilters = new ArrayList<>();
|
||||
chatFilters.add(chatFilter);
|
||||
semanticParseInfo.setDimensionFilters(chatFilters);
|
||||
|
||||
QueryResultWithSchemaResp queryResultWithColumns = null;
|
||||
try {
|
||||
queryResultWithColumns = semanticLayer.queryByStruct(SchemaInfoConverter.convertTo(semanticParseInfo),
|
||||
user);
|
||||
} catch (Exception e) {
|
||||
logger.warn("setMainDomain queryByStruct error, e:", e);
|
||||
}
|
||||
|
||||
if (queryResultWithColumns != null) {
|
||||
if (!CollectionUtils.isEmpty(queryResultWithColumns.getResultList())
|
||||
&& queryResultWithColumns.getResultList().size() > 0) {
|
||||
Map<String, Object> result = queryResultWithColumns.getResultList().get(0);
|
||||
for (Map.Entry<String, Object> entry : result.entrySet()) {
|
||||
String entryKey = getEntryKey(entry);
|
||||
if (entry.getValue() == null || entryKey == null) {
|
||||
continue;
|
||||
}
|
||||
domainInfo.getDimensions().stream().filter(i -> entryKey.equals(i.getBizName()))
|
||||
.forEach(i -> i.setValue(entry.getValue().toString()));
|
||||
domainInfo.getMetrics().stream().filter(i -> entryKey.equals(i.getBizName()))
|
||||
.forEach(i -> i.setValue(entry.getValue().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SchemaItem> getDimensions(EntityInfo domainInfo) {
|
||||
List<SchemaItem> dimensions = new ArrayList<>();
|
||||
for (DataInfo mainEntityDimension : domainInfo.getDimensions()) {
|
||||
SchemaItem dimension = new SchemaItem();
|
||||
dimension.setBizName(mainEntityDimension.getBizName());
|
||||
dimensions.add(dimension);
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
private String getEntryKey(Map.Entry<String, Object> entry) {
|
||||
// metric parser special handle, TODO delete
|
||||
String entryKey = entry.getKey();
|
||||
if (entryKey.contains("__")) {
|
||||
entryKey = entryKey.split("__")[1];
|
||||
}
|
||||
return entryKey;
|
||||
}
|
||||
|
||||
private List<SchemaItem> getMetrics(EntityInfo domainInfo) {
|
||||
List<SchemaItem> metrics = new ArrayList<>();
|
||||
for (DataInfo metricValue : domainInfo.getMetrics()) {
|
||||
SchemaItem metric = new SchemaItem();
|
||||
metric.setBizName(metricValue.getBizName());
|
||||
metrics.add(metric);
|
||||
}
|
||||
return metrics;
|
||||
}
|
||||
|
||||
private String getEntityPrimaryName(EntityInfo domainInfo) {
|
||||
return domainInfo.getDomainInfo().getPrimaryEntityBizName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.tencent.supersonic.chat.application;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.response.QueryResultResp;
|
||||
import com.tencent.supersonic.chat.api.service.SchemaMapper;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticLayer;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticParser;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticQuery;
|
||||
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
|
||||
import com.tencent.supersonic.chat.application.query.SemanticQueryFactory;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.QueryData;
|
||||
import com.tencent.supersonic.chat.domain.service.ChatService;
|
||||
import com.tencent.supersonic.chat.domain.service.QueryService;
|
||||
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
|
||||
import com.tencent.supersonic.common.util.json.JsonUtil;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class QueryServiceImpl implements QueryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(QueryServiceImpl.class);
|
||||
private List<SchemaMapper> schemaMappers;
|
||||
private List<SemanticParser> semanticParsers;
|
||||
@Autowired
|
||||
private ChatService chatService;
|
||||
@Autowired
|
||||
private SemanticLayer semanticLayer;
|
||||
|
||||
public QueryServiceImpl() {
|
||||
schemaMappers = SpringFactoriesLoader.loadFactories(SchemaMapper.class,
|
||||
Thread.currentThread().getContextClassLoader());
|
||||
semanticParsers = SpringFactoriesLoader.loadFactories(SemanticParser.class,
|
||||
Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
public QueryResultResp executeQuery(QueryContextReq queryCtx) throws Exception {
|
||||
schemaMappers.stream().forEach(s -> s.map(queryCtx));
|
||||
|
||||
// in order to support multi-turn conversation, we need to consider chat context
|
||||
ChatContext chatCtx = chatService.getOrCreateContext(queryCtx.getChatId());
|
||||
|
||||
for (SemanticParser semanticParser : semanticParsers) {
|
||||
logger.info("semanticParser processing:{}", JsonUtil.prettyToString(semanticParser));
|
||||
boolean isFinish = semanticParser.parse(queryCtx, chatCtx);
|
||||
if (isFinish) {
|
||||
logger.info("semanticParser is finish ,semanticParser:{}", semanticParser.getClass().getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
// submit semantic query based on the result of semantic parsing
|
||||
SemanticQuery query = SemanticQueryFactory.get(queryCtx.getParseInfo().getQueryMode());
|
||||
|
||||
QueryResultResp queryResponse = query.execute(queryCtx, chatCtx);
|
||||
|
||||
// update chat context after a successful semantic query
|
||||
query.updateContext(queryResponse, chatCtx, queryCtx);
|
||||
|
||||
chatService.addQuery(queryResponse, queryCtx, chatCtx);
|
||||
|
||||
return queryResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo queryContext(QueryContextReq queryCtx) {
|
||||
ChatContext context = chatService.getOrCreateContext(queryCtx.getChatId());
|
||||
return context.getParseInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResultResp queryData(QueryData queryData, User user) throws Exception {
|
||||
SemanticParseInfo semanticParseInfo = new SemanticParseInfo();
|
||||
QueryResultResp queryResponse = new QueryResultResp();
|
||||
BeanUtils.copyProperties(queryData, semanticParseInfo);
|
||||
QueryResultWithSchemaResp resultWithColumns = semanticLayer.queryByStruct(
|
||||
SchemaInfoConverter.convertTo(semanticParseInfo), user);
|
||||
queryResponse.setQueryColumns(resultWithColumns.getColumns());
|
||||
queryResponse.setQueryResults(resultWithColumns.getResultList());
|
||||
return queryResponse;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.tencent.supersonic.chat.application;
|
||||
|
||||
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticLayer;
|
||||
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.RecommendResponse;
|
||||
import com.tencent.supersonic.chat.domain.service.RecommendService;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/***
|
||||
* Recommend Service impl
|
||||
*/
|
||||
@Service
|
||||
public class RecommendServiceImpl implements RecommendService {
|
||||
|
||||
@Autowired
|
||||
private SemanticLayer semanticLayer;
|
||||
|
||||
@Override
|
||||
public RecommendResponse recommend(QueryContextReq queryCtx) {
|
||||
Integer domainId = queryCtx.getDomainId();
|
||||
if (Objects.isNull(domainId)) {
|
||||
return new RecommendResponse();
|
||||
}
|
||||
|
||||
DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(
|
||||
Long.valueOf(domainId));
|
||||
|
||||
List<RecommendResponse.Item> dimensions = domainSchemaDesc.getDimensions().stream().map(dimSchemaDesc -> {
|
||||
RecommendResponse.Item item = new RecommendResponse.Item();
|
||||
item.setDomain(domainId);
|
||||
item.setName(dimSchemaDesc.getName());
|
||||
item.setBizName(dimSchemaDesc.getBizName());
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
List<RecommendResponse.Item> metrics = domainSchemaDesc.getMetrics().stream().map(metricSchemaDesc -> {
|
||||
RecommendResponse.Item item = new RecommendResponse.Item();
|
||||
item.setDomain(domainId);
|
||||
item.setName(metricSchemaDesc.getName());
|
||||
item.setBizName(metricSchemaDesc.getBizName());
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
RecommendResponse response = new RecommendResponse();
|
||||
response.setDimensions(dimensions);
|
||||
response.setMetrics(metrics);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package com.tencent.supersonic.chat.application;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.hankcs.hanlp.seg.common.Term;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticLayer;
|
||||
import com.tencent.supersonic.chat.application.knowledge.NatureHelper;
|
||||
import com.tencent.supersonic.chat.application.mapper.SearchMatchStrategy;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.DomainInfoStat;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.DomainWithSemanticType;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.MatchText;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.SearchResult;
|
||||
import com.tencent.supersonic.chat.domain.pojo.semantic.DomainInfos;
|
||||
import com.tencent.supersonic.chat.domain.service.ChatService;
|
||||
import com.tencent.supersonic.chat.domain.service.SearchService;
|
||||
import com.tencent.supersonic.chat.domain.utils.NatureConverter;
|
||||
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
|
||||
import com.tencent.supersonic.common.nlp.ItemDO;
|
||||
import com.tencent.supersonic.common.nlp.MapResult;
|
||||
import com.tencent.supersonic.common.nlp.NatureType;
|
||||
import com.tencent.supersonic.common.nlp.WordNature;
|
||||
import com.tencent.supersonic.knowledge.application.online.BaseWordNature;
|
||||
import com.tencent.supersonic.knowledge.infrastructure.nlp.HanlpHelper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* search service impl
|
||||
*/
|
||||
@Service
|
||||
public class SearchServiceImpl implements SearchService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SearchServiceImpl.class);
|
||||
@Autowired
|
||||
private SemanticLayer semanticLayer;
|
||||
@Autowired
|
||||
private ChatService chatService;
|
||||
|
||||
@Autowired
|
||||
private SearchMatchStrategy searchMatchStrategy;
|
||||
|
||||
private static final int RESULT_SIZE = 10;
|
||||
|
||||
|
||||
@Override
|
||||
public List<SearchResult> search(QueryContextReq queryCtx) {
|
||||
String queryText = queryCtx.getQueryText();
|
||||
// 1.get meta info
|
||||
DomainInfos domainInfosDb = SchemaInfoConverter.convert(semanticLayer.getDomainSchemaInfo(new ArrayList<>()));
|
||||
List<ItemDO> metricsDb = domainInfosDb.getMetrics();
|
||||
final Map<Integer, String> domainToName = domainInfosDb.getDomainToName();
|
||||
// 2.detect by segment
|
||||
List<Term> originals = HanlpHelper.getSegment().seg(queryText).stream().collect(Collectors.toList());
|
||||
Map<MatchText, List<MapResult>> regTextMap = searchMatchStrategy.matchWithMatchText(queryText, originals);
|
||||
|
||||
// 3.get the most matching data
|
||||
Optional<Entry<MatchText, List<MapResult>>> mostSimilarSearchResult = regTextMap.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> CollectionUtils.isNotEmpty(entry.getValue()))
|
||||
.reduce((entry1, entry2) ->
|
||||
entry1.getKey().getDetectSegment().length() >= entry2.getKey().getDetectSegment().length()
|
||||
? entry1 : entry2);
|
||||
logger.debug("mostSimilarSearchResult:{}", mostSimilarSearchResult);
|
||||
// 4.optimize the results after the query
|
||||
if (!mostSimilarSearchResult.isPresent()) {
|
||||
logger.info("unable to find any information through search , queryCtx:{}", queryCtx);
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
Map.Entry<MatchText, List<MapResult>> searchTextEntry = mostSimilarSearchResult.get();
|
||||
logger.info("searchTextEntry:{},queryCtx:{}", searchTextEntry, queryCtx);
|
||||
|
||||
Set<SearchResult> searchResults = new LinkedHashSet();
|
||||
DomainInfoStat domainStat = NatureHelper.getDomainStat(originals);
|
||||
|
||||
List<Integer> possibleDomains = getPossibleDomains(queryCtx, originals, domainStat);
|
||||
|
||||
// 4.1 priority dimension metric
|
||||
boolean existMetricAndDimension = searchMetricAndDimension(new HashSet<>(possibleDomains), domainToName,
|
||||
searchTextEntry,
|
||||
searchResults);
|
||||
|
||||
// 4.2 process based on dimension values
|
||||
MatchText matchText = searchTextEntry.getKey();
|
||||
Map<String, String> natureToNameMap = getNatureToNameMap(searchTextEntry);
|
||||
|
||||
for (Map.Entry<String, String> natureToNameEntry : natureToNameMap.entrySet()) {
|
||||
searchDimensionValue(metricsDb, domainToName, domainStat.getMetricDomainCount(), searchResults,
|
||||
existMetricAndDimension, matchText, natureToNameMap, natureToNameEntry);
|
||||
}
|
||||
return searchResults.stream().limit(RESULT_SIZE).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Integer> getPossibleDomains(QueryContextReq queryCtx, List<Term> originals,
|
||||
DomainInfoStat domainStat) {
|
||||
List<Integer> possibleDomains = NatureHelper.selectPossibleDomains(originals);
|
||||
|
||||
Long contextDomain = chatService.getContextDomain(queryCtx.getChatId());
|
||||
|
||||
logger.debug("possibleDomains:{},domainStat:{},contextDomain:{}", possibleDomains, domainStat, contextDomain);
|
||||
|
||||
// If nothing is recognized or only metric are present, then add the contextDomain.
|
||||
if (nothingOrOnlyMetric(domainStat) && effectiveDomain(contextDomain)) {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
result.add(Math.toIntExact(contextDomain));
|
||||
return result;
|
||||
}
|
||||
return possibleDomains;
|
||||
}
|
||||
|
||||
private boolean nothingOrOnlyMetric(DomainInfoStat domainStat) {
|
||||
return domainStat.getMetricDomainCount() >= 0 && domainStat.getDimensionDomainCount() <= 0
|
||||
&& domainStat.getDimensionValueDomainCount() <= 0 && domainStat.getDomainCount() <= 0;
|
||||
}
|
||||
|
||||
private boolean effectiveDomain(Long contextDomain) {
|
||||
return Objects.nonNull(contextDomain) && contextDomain > 0;
|
||||
}
|
||||
|
||||
private void searchDimensionValue(List<ItemDO> metricsDb,
|
||||
Map<Integer, String> domainToName,
|
||||
long metricDomainCount,
|
||||
Set<SearchResult> searchResults,
|
||||
boolean existMetricAndDimension,
|
||||
MatchText matchText,
|
||||
Map<String, String> natureToNameMap,
|
||||
Map.Entry<String, String> natureToNameEntry) {
|
||||
String nature = natureToNameEntry.getKey();
|
||||
String wordName = natureToNameEntry.getValue();
|
||||
|
||||
Integer domain = BaseWordNature.getDomain(nature);
|
||||
SchemaElementType schemaElementType = NatureConverter.convertTo(nature);
|
||||
|
||||
if (SchemaElementType.ENTITY.equals(schemaElementType)) {
|
||||
return;
|
||||
}
|
||||
// If there are no metric/dimension, complete the metric information
|
||||
if (metricDomainCount <= 0 && !existMetricAndDimension) {
|
||||
searchResults.add(
|
||||
new SearchResult(matchText.getRegText() + wordName, wordName, domainToName.get(domain), domain,
|
||||
schemaElementType));
|
||||
int metricSize = RESULT_SIZE / (natureToNameMap.entrySet().size());
|
||||
if (metricSize <= 1) {
|
||||
metricSize = 1;
|
||||
}
|
||||
List<String> metrics = filerMetricsByDomain(metricsDb, domain).stream().limit(metricSize).collect(
|
||||
Collectors.toList());
|
||||
;
|
||||
for (String metric : metrics) {
|
||||
String subRecommend = matchText.getRegText() + wordName + NatureType.SPACE + metric;
|
||||
searchResults.add(
|
||||
new SearchResult(subRecommend, wordName + NatureType.SPACE + metric, domainToName.get(domain),
|
||||
domain, false));
|
||||
}
|
||||
} else {
|
||||
searchResults.add(
|
||||
new SearchResult(matchText.getRegText() + wordName, wordName, domainToName.get(domain), domain,
|
||||
schemaElementType));
|
||||
}
|
||||
}
|
||||
|
||||
protected List<String> filerMetricsByDomain(List<ItemDO> metricsDb, Integer domain) {
|
||||
if (CollectionUtils.isEmpty(metricsDb)) {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
return metricsDb.stream()
|
||||
.filter(mapDO -> Objects.nonNull(mapDO) && domain.equals(mapDO.getDomain()))
|
||||
.sorted(Comparator.comparing(ItemDO::getUseCnt).reversed())
|
||||
.flatMap(entry -> {
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(entry.getName());
|
||||
return result.stream();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/***
|
||||
* convert nature to name
|
||||
* @param recommendTextListEntry
|
||||
* @return
|
||||
*/
|
||||
private Map<String, String> getNatureToNameMap(Map.Entry<MatchText, List<MapResult>> recommendTextListEntry) {
|
||||
List<MapResult> recommendValues = recommendTextListEntry.getValue();
|
||||
return recommendValues.stream()
|
||||
.flatMap(entry -> entry.getNatures().stream().map(nature -> {
|
||||
WordNature posDO = new WordNature();
|
||||
posDO.setWord(entry.getName());
|
||||
posDO.setNature(nature);
|
||||
return posDO;
|
||||
}
|
||||
)).sorted(Comparator.comparingInt(a -> a.getWord().length()))
|
||||
.collect(Collectors.toMap(WordNature::getNature, WordNature::getWord, (value1, value2) -> value1,
|
||||
LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private boolean searchMetricAndDimension(Set<Integer> possibleDomains, Map<Integer, String> domainToName,
|
||||
Map.Entry<MatchText, List<MapResult>> searchTextEntry, Set<SearchResult> searchResults) {
|
||||
boolean existMetric = false;
|
||||
|
||||
MatchText matchText = searchTextEntry.getKey();
|
||||
List<MapResult> mapResults = searchTextEntry.getValue();
|
||||
|
||||
for (MapResult mapResult : mapResults) {
|
||||
|
||||
List<DomainWithSemanticType> dimensionMetricClassIds = mapResult.getNatures().stream()
|
||||
.map(nature -> new DomainWithSemanticType(BaseWordNature.getDomain(nature),
|
||||
NatureConverter.convertTo(nature)))
|
||||
.filter(entry -> matchCondition(entry, possibleDomains)).collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isNotEmpty(dimensionMetricClassIds)) {
|
||||
for (DomainWithSemanticType domainWithSemanticType : dimensionMetricClassIds) {
|
||||
existMetric = true;
|
||||
Integer domain = domainWithSemanticType.getDomain();
|
||||
SchemaElementType semanticType = domainWithSemanticType.getSemanticType();
|
||||
searchResults.add(
|
||||
new SearchResult(matchText.getRegText() + mapResult.getName(), mapResult.getName(),
|
||||
domainToName.get(domain), domain, semanticType));
|
||||
}
|
||||
}
|
||||
logger.info("parseResult:{},dimensionMetricClassIds:{},possibleDomains:{}", mapResult,
|
||||
dimensionMetricClassIds, possibleDomains);
|
||||
}
|
||||
return existMetric;
|
||||
}
|
||||
|
||||
private boolean matchCondition(DomainWithSemanticType entry, Set<Integer> possibleDomains) {
|
||||
if (!(SchemaElementType.METRIC.equals(entry.getSemanticType()) || SchemaElementType.DIMENSION.equals(
|
||||
entry.getSemanticType()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(possibleDomains)) {
|
||||
return true;
|
||||
}
|
||||
return possibleDomains.contains(entry.getDomain());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.tencent.supersonic.chat.application.knowledge;
|
||||
|
||||
import com.tencent.supersonic.common.nlp.WordNature;
|
||||
import com.tencent.supersonic.knowledge.domain.service.OnlineKnowledgeService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ApplicationStartedInit implements ApplicationListener<ApplicationStartedEvent> {
|
||||
|
||||
@Autowired
|
||||
private OnlineKnowledgeService onlineKnowledgeService;
|
||||
|
||||
@Autowired
|
||||
private WordNatureService wordNatureService;
|
||||
|
||||
private List<WordNature> preWordNatures = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationStartedEvent event) {
|
||||
try {
|
||||
log.info("ApplicationStartedInit start");
|
||||
|
||||
List<WordNature> wordNatures = wordNatureService.getAllWordNature();
|
||||
|
||||
this.preWordNatures = wordNatures;
|
||||
|
||||
onlineKnowledgeService.reloadAllData(wordNatures);
|
||||
|
||||
log.info("ApplicationStartedInit end");
|
||||
} catch (Exception e) {
|
||||
log.error("ApplicationStartedInit error", e);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* reload knowledge task
|
||||
*/
|
||||
@Scheduled(cron = "${reload.knowledge.corn:0 0/1 * * * ?}")
|
||||
public void reloadKnowledge() {
|
||||
log.debug("reloadKnowledge start");
|
||||
|
||||
try {
|
||||
List<WordNature> wordNatures = wordNatureService.getAllWordNature();
|
||||
|
||||
if (CollectionUtils.isEqualCollection(wordNatures, preWordNatures)) {
|
||||
log.debug("wordNatures is not change, reloadKnowledge end");
|
||||
return;
|
||||
}
|
||||
this.preWordNatures = wordNatures;
|
||||
|
||||
onlineKnowledgeService.updateOnlineKnowledge(wordNatureService.getAllWordNature());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("reloadKnowledge error", e);
|
||||
}
|
||||
|
||||
log.debug("reloadKnowledge end");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.tencent.supersonic.chat.application.knowledge;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.chat.domain.dataobject.DimValueDO;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.DefaultMetric;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.Dim4Dict;
|
||||
import com.tencent.supersonic.chat.domain.utils.DictMetaUtils;
|
||||
import com.tencent.supersonic.chat.domain.utils.DictQueryUtils;
|
||||
import com.tencent.supersonic.common.constant.Constants;
|
||||
import com.tencent.supersonic.common.enums.TaskStatusEnum;
|
||||
import com.tencent.supersonic.knowledge.domain.FileHandler;
|
||||
import com.tencent.supersonic.knowledge.domain.converter.DictTaskConverter;
|
||||
import com.tencent.supersonic.knowledge.domain.dataobject.DimValueDictTaskPO;
|
||||
import com.tencent.supersonic.knowledge.domain.pojo.DictConfig;
|
||||
import com.tencent.supersonic.knowledge.domain.pojo.DictTaskFilter;
|
||||
import com.tencent.supersonic.knowledge.domain.pojo.DictUpdateMode;
|
||||
import com.tencent.supersonic.knowledge.domain.pojo.DimValue2DictCommand;
|
||||
import com.tencent.supersonic.knowledge.domain.pojo.DimValueDictInfo;
|
||||
import com.tencent.supersonic.knowledge.domain.repository.DictRepository;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DictApplicationService {
|
||||
|
||||
@Value("${dict.flush.enable:true}")
|
||||
private Boolean dictFlushEnable;
|
||||
|
||||
@Value("${dict.file.type:txt}")
|
||||
private String dictFileType;
|
||||
private String dimValue = "DimValue_%d_%d";
|
||||
private String dateTimeFormatter = "yyyyMMddHHmmss";
|
||||
|
||||
|
||||
private final DictMetaUtils metaUtils;
|
||||
private final DictQueryUtils dictQueryUtils;
|
||||
private final FileHandler fileHandler;
|
||||
private final DictRepository dictRepository;
|
||||
|
||||
public DictApplicationService(DictMetaUtils metaUtils,
|
||||
DictQueryUtils dictQueryUtils,
|
||||
FileHandler fileHandler,
|
||||
DictRepository dictRepository) {
|
||||
this.metaUtils = metaUtils;
|
||||
this.dictQueryUtils = dictQueryUtils;
|
||||
this.fileHandler = fileHandler;
|
||||
this.dictRepository = dictRepository;
|
||||
}
|
||||
|
||||
public Long addDictTask(DimValue2DictCommand dimValue2DictCommend, User user) {
|
||||
if (!dictFlushEnable) {
|
||||
return 0L;
|
||||
}
|
||||
DimValueDictTaskPO dimValueDictTaskPO = DictTaskConverter.generateDimValueDictTaskPO(dimValue2DictCommend,
|
||||
user);
|
||||
log.info("[addDictTask] dimValueDictTaskPO:{}", dimValueDictTaskPO);
|
||||
dictRepository.createDimValueDictTask(dimValueDictTaskPO);
|
||||
TaskStatusEnum finalStatus = TaskStatusEnum.SUCCESS;
|
||||
try {
|
||||
//1. construct internal dictionary requirements
|
||||
List<DimValueDO> dimValueDOList = metaUtils.generateDimValueInfo(dimValue2DictCommend);
|
||||
log.info("dimValueDOList:{}", dimValueDOList);
|
||||
//2. query dimension value information
|
||||
for (DimValueDO dimValueDO : dimValueDOList) {
|
||||
Long domainId = dimValueDO.getDomainId();
|
||||
DefaultMetric defaultMetricDesc = dimValueDO.getDefaultMetricDescList().get(0);
|
||||
for (Dim4Dict dim4Dict : dimValueDO.getDimensions()) {
|
||||
List<String> data = dictQueryUtils.fetchDimValueSingle(domainId, defaultMetricDesc, dim4Dict, user);
|
||||
//3. local file changes
|
||||
String fileName = String.format(dimValue + Constants.DOT + dictFileType, domainId,
|
||||
dim4Dict.getDimId());
|
||||
fileHandler.writeFile(data, fileName, false);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("addDictInfo exception:", e);
|
||||
finalStatus = TaskStatusEnum.ERROR;
|
||||
}
|
||||
dictRepository.updateDictTaskStatus(finalStatus.getCode(), dimValueDictTaskPO);
|
||||
return 1L;
|
||||
}
|
||||
|
||||
|
||||
public Long deleteDictTask(DimValue2DictCommand dimValue2DictCommend, User user) {
|
||||
if (!dictFlushEnable) {
|
||||
return 0L;
|
||||
}
|
||||
if (Objects.isNull(dimValue2DictCommend) || DictUpdateMode.REALTIME_DELETE.equals(
|
||||
dimValue2DictCommend.getUpdateMode())) {
|
||||
throw new RuntimeException("illegal parameter");
|
||||
}
|
||||
Map<Long, List<Long>> domainAndDimPair = dimValue2DictCommend.getDomainAndDimPair();
|
||||
if (CollectionUtils.isEmpty(domainAndDimPair)) {
|
||||
return 0L;
|
||||
}
|
||||
for (Long domainId : domainAndDimPair.keySet()) {
|
||||
if (CollectionUtils.isEmpty(domainAndDimPair.get(domainId))) {
|
||||
continue;
|
||||
}
|
||||
for (Long dimId : domainAndDimPair.get(domainId)) {
|
||||
String fileName = String.format(dimValue + Constants.DOT + dictFileType, domainId, dimId);
|
||||
fileHandler.deleteDictFile(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
return 1L;
|
||||
}
|
||||
|
||||
public String getDictRootPath() {
|
||||
return fileHandler.getDictRootPath();
|
||||
}
|
||||
|
||||
|
||||
public List<DimValueDictInfo> searchDictTaskList(DictTaskFilter filter, User user) {
|
||||
return dictRepository.searchDictTaskList(filter);
|
||||
}
|
||||
|
||||
public DictConfig getDictInfoByDomainId(Long domainId) {
|
||||
return dictRepository.getDictInfoByDomainId(domainId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.tencent.supersonic.chat.application.knowledge;
|
||||
|
||||
import com.hankcs.hanlp.corpus.tag.Nature;
|
||||
import com.hankcs.hanlp.seg.common.Term;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.DomainInfoStat;
|
||||
import com.tencent.supersonic.common.nlp.NatureType;
|
||||
import com.tencent.supersonic.knowledge.application.online.BaseWordNature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* nature parse helper
|
||||
*/
|
||||
public class NatureHelper {
|
||||
|
||||
|
||||
private static boolean isDomainOrEntity(Term term, Integer domain) {
|
||||
return (NatureType.NATURE_SPILT + domain).equals(term.nature.toString()) || term.nature.toString()
|
||||
.endsWith(NatureType.ENTITY.getType());
|
||||
}
|
||||
|
||||
public static Integer getDomainByNature(Nature nature) {
|
||||
if (nature.startsWith(NatureType.NATURE_SPILT)) {
|
||||
String[] dimensionValues = nature.toString().split(NatureType.NATURE_SPILT);
|
||||
if (StringUtils.isNumeric(dimensionValues[1])) {
|
||||
return Integer.valueOf(dimensionValues[1]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Integer getDomain(String nature) {
|
||||
String[] split = nature.split(NatureType.NATURE_SPILT);
|
||||
return Integer.valueOf(split[1]);
|
||||
}
|
||||
|
||||
public static boolean isDimensionValueClassId(String nature) {
|
||||
if (StringUtils.isEmpty(nature)) {
|
||||
return false;
|
||||
}
|
||||
if (!nature.startsWith(NatureType.NATURE_SPILT)) {
|
||||
return false;
|
||||
}
|
||||
String[] split = nature.split(NatureType.NATURE_SPILT);
|
||||
if (split.length <= 1) {
|
||||
return false;
|
||||
}
|
||||
return !nature.endsWith(NatureType.METRIC.getType()) && !nature.endsWith(NatureType.DIMENSION.getType())
|
||||
&& StringUtils.isNumeric(split[1]);
|
||||
}
|
||||
|
||||
public static DomainInfoStat getDomainStat(List<Term> terms) {
|
||||
DomainInfoStat stat = new DomainInfoStat();
|
||||
stat.setDimensionDomainCount(getDimensionCount(terms));
|
||||
stat.setMetricDomainCount(getMetricCount(terms));
|
||||
stat.setDomainCount(getDomainCount(terms));
|
||||
stat.setDimensionValueDomainCount(getDimensionValueCount(terms));
|
||||
return stat;
|
||||
}
|
||||
|
||||
|
||||
private static long getDomainCount(List<Term> terms) {
|
||||
return terms.stream().filter(term -> isDomainOrEntity(term, getDomainByNature(term.nature))).count();
|
||||
}
|
||||
|
||||
private static long getDimensionValueCount(List<Term> terms) {
|
||||
return terms.stream().filter(term -> isDimensionValueClassId(term.nature.toString())).count();
|
||||
}
|
||||
|
||||
private static long getDimensionCount(List<Term> terms) {
|
||||
return terms.stream().filter(term -> term.nature.startsWith(NatureType.NATURE_SPILT) && term.nature.toString()
|
||||
.endsWith(NatureType.DIMENSION.getType())).count();
|
||||
}
|
||||
|
||||
private static long getMetricCount(List<Term> terms) {
|
||||
return terms.stream().filter(term -> term.nature.startsWith(NatureType.NATURE_SPILT) && term.nature.toString()
|
||||
.endsWith(NatureType.METRIC.getType())).count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of types of class parts of speech
|
||||
* classId -> (nature , natureCount)
|
||||
*
|
||||
* @param terms
|
||||
* @return
|
||||
*/
|
||||
public static Map<Integer, Map<NatureType, Integer>> getDomainToNatureStat(List<Term> terms) {
|
||||
Map<Integer, Map<NatureType, Integer>> domainToNature = new HashMap<>();
|
||||
terms.stream().filter(
|
||||
term -> term.nature.startsWith(NatureType.NATURE_SPILT)
|
||||
).forEach(term -> {
|
||||
NatureType natureType = NatureType.getNatureType(String.valueOf(term.nature));
|
||||
Integer domain = BaseWordNature.getDomain(String.valueOf(term.nature));
|
||||
|
||||
Map<NatureType, Integer> natureTypeMap = new HashMap<>();
|
||||
natureTypeMap.put(natureType, 1);
|
||||
|
||||
Map<NatureType, Integer> original = domainToNature.get(domain);
|
||||
if (Objects.isNull(original)) {
|
||||
domainToNature.put(domain, natureTypeMap);
|
||||
} else {
|
||||
Integer count = original.get(natureType);
|
||||
if (Objects.isNull(count)) {
|
||||
count = 1;
|
||||
} else {
|
||||
count = count + 1;
|
||||
}
|
||||
original.put(natureType, count);
|
||||
}
|
||||
});
|
||||
return domainToNature;
|
||||
}
|
||||
|
||||
public static List<Integer> selectPossibleDomains(List<Term> terms) {
|
||||
Map<Integer, Map<NatureType, Integer>> domainToNatureStat = getDomainToNatureStat(terms);
|
||||
Integer maxDomainTypeSize = domainToNatureStat.entrySet().stream()
|
||||
.max(Comparator.comparingInt(o -> o.getValue().size())).map(entry -> entry.getValue().size())
|
||||
.orElse(null);
|
||||
if (Objects.isNull(maxDomainTypeSize) || maxDomainTypeSize == 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return domainToNatureStat.entrySet().stream().filter(entry -> entry.getValue().size() == maxDomainTypeSize)
|
||||
.map(entry -> entry.getKey()).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.tencent.supersonic.chat.application.knowledge;
|
||||
|
||||
import com.tencent.supersonic.chat.api.service.SemanticLayer;
|
||||
import com.tencent.supersonic.chat.domain.pojo.semantic.DomainInfos;
|
||||
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
|
||||
import com.tencent.supersonic.common.nlp.ItemDO;
|
||||
import com.tencent.supersonic.common.nlp.NatureType;
|
||||
import com.tencent.supersonic.common.nlp.WordNature;
|
||||
import com.tencent.supersonic.knowledge.application.online.WordNatureStrategyFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* word nature service
|
||||
**/
|
||||
@Service
|
||||
public class WordNatureService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WordNatureService.class);
|
||||
|
||||
@Autowired
|
||||
private SemanticLayer semanticLayer;
|
||||
|
||||
public List<WordNature> getAllWordNature() {
|
||||
|
||||
DomainInfos domainInfos = SchemaInfoConverter.convert(semanticLayer.getDomainSchemaInfo(new ArrayList<>()));
|
||||
|
||||
List<WordNature> natures = new ArrayList<>();
|
||||
|
||||
addNatureToResult(NatureType.DIMENSION, domainInfos.getDimensions(), natures);
|
||||
|
||||
addNatureToResult(NatureType.METRIC, domainInfos.getMetrics(), natures);
|
||||
|
||||
addNatureToResult(NatureType.DOMAIN, domainInfos.getDomains(), natures);
|
||||
|
||||
addNatureToResult(NatureType.ENTITY, domainInfos.getEntities(), natures);
|
||||
|
||||
return natures;
|
||||
}
|
||||
|
||||
private void addNatureToResult(NatureType value, List<ItemDO> metas, List<WordNature> natures) {
|
||||
List<WordNature> natureList = WordNatureStrategyFactory.get(value).getWordNatureList(metas);
|
||||
logger.debug("nature type:{} , nature size:{}", value.name(), natureList.size());
|
||||
natures.addAll(natureList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.tencent.supersonic.chat.application.mapper;
|
||||
|
||||
import com.hankcs.hanlp.seg.common.Term;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SchemaMapper;
|
||||
import com.tencent.supersonic.chat.application.knowledge.NatureHelper;
|
||||
import com.tencent.supersonic.chat.domain.utils.NatureConverter;
|
||||
import com.tencent.supersonic.common.nlp.MapResult;
|
||||
import com.tencent.supersonic.common.nlp.NatureType;
|
||||
import com.tencent.supersonic.common.util.context.ContextUtils;
|
||||
import com.tencent.supersonic.common.util.json.JsonUtil;
|
||||
import com.tencent.supersonic.knowledge.application.online.WordNatureStrategyFactory;
|
||||
import com.tencent.supersonic.knowledge.infrastructure.nlp.HanlpHelper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class HanlpSchemaMapper implements SchemaMapper {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HanlpSchemaMapper.class);
|
||||
|
||||
@Override
|
||||
public void map(QueryContextReq searchCtx) {
|
||||
|
||||
List<Term> terms = HanlpHelper.getSegment().seg(searchCtx.getQueryText()).stream().collect(Collectors.toList());
|
||||
terms.forEach(
|
||||
item -> LOGGER.info("word:{},nature:{},frequency:{}", item.word, item.nature.toString(), item.frequency)
|
||||
);
|
||||
QueryMatchStrategy matchStrategy = ContextUtils.getBean(QueryMatchStrategy.class);
|
||||
|
||||
List<MapResult> matches = matchStrategy.match(searchCtx.getQueryText(), terms, 0);
|
||||
|
||||
convertTermsToSchemaMapInfo(matches, searchCtx.getMapInfo());
|
||||
}
|
||||
|
||||
private void convertTermsToSchemaMapInfo(List<MapResult> mapResults, SchemaMapInfo schemaMap) {
|
||||
if (CollectionUtils.isEmpty(mapResults)) {
|
||||
return;
|
||||
}
|
||||
for (MapResult mapResult : mapResults) {
|
||||
for (String nature : mapResult.getNatures()) {
|
||||
Integer domain = NatureHelper.getDomain(nature);
|
||||
if (Objects.isNull(domain)) {
|
||||
continue;
|
||||
}
|
||||
SchemaElementType elementType = NatureConverter.convertTo(nature);
|
||||
if (Objects.isNull(elementType)) {
|
||||
continue;
|
||||
}
|
||||
SchemaElementMatch schemaElementMatch = new SchemaElementMatch();
|
||||
|
||||
schemaElementMatch.setElementType(elementType);
|
||||
Integer elementID = WordNatureStrategyFactory.get(NatureType.getNatureType(nature))
|
||||
.getElementID(nature);
|
||||
schemaElementMatch.setElementID(elementID);
|
||||
schemaElementMatch.setWord(mapResult.getName());
|
||||
schemaElementMatch.setSimilarity(mapResult.getSimilarity());
|
||||
|
||||
Map<Integer, List<SchemaElementMatch>> domainElementMatches = schemaMap.getDomainElementMatches();
|
||||
List<SchemaElementMatch> schemaElementMatches = domainElementMatches.putIfAbsent(domain,
|
||||
new ArrayList<>());
|
||||
if (schemaElementMatches == null) {
|
||||
schemaElementMatches = domainElementMatches.get(domain);
|
||||
}
|
||||
schemaElementMatches.add(schemaElementMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.tencent.supersonic.chat.application.mapper;
|
||||
|
||||
import com.hankcs.hanlp.algorithm.EditDistance;
|
||||
import com.hankcs.hanlp.seg.common.Term;
|
||||
import com.tencent.supersonic.chat.application.knowledge.NatureHelper;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.MatchText;
|
||||
import com.tencent.supersonic.common.nlp.MapResult;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* match strategy
|
||||
*/
|
||||
public interface MatchStrategy {
|
||||
|
||||
/***
|
||||
* match
|
||||
* @param terms
|
||||
* @return
|
||||
*/
|
||||
List<MapResult> match(String text, List<Term> terms, int retryCount);
|
||||
|
||||
|
||||
List<MapResult> match(String text, List<Term> terms, int retryCount, Integer detectDomainId);
|
||||
|
||||
|
||||
Map<MatchText, List<MapResult>> matchWithMatchText(String text, List<Term> originals);
|
||||
|
||||
/***
|
||||
* exist dimension values
|
||||
* @param natures
|
||||
* @return
|
||||
*/
|
||||
default boolean existDimensionValues(List<String> natures) {
|
||||
for (String nature : natures) {
|
||||
if (NatureHelper.isDimensionValueClassId(nature)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/***
|
||||
* get similarity
|
||||
* @param detectSegment
|
||||
* @param matchName
|
||||
* @return
|
||||
*/
|
||||
default double getSimilarity(String detectSegment, String matchName) {
|
||||
return 1 - (double) EditDistance.compute(detectSegment, matchName) / Math.max(matchName.length(),
|
||||
detectSegment.length());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.tencent.supersonic.chat.application.mapper;
|
||||
|
||||
import com.hankcs.hanlp.seg.common.Term;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.MatchText;
|
||||
import com.tencent.supersonic.common.nlp.MapResult;
|
||||
import com.tencent.supersonic.common.nlp.NatureType;
|
||||
import com.tencent.supersonic.knowledge.infrastructure.nlp.Suggester;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.compress.utils.Lists;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* match strategy implement
|
||||
*/
|
||||
@Service
|
||||
public class QueryMatchStrategy implements MatchStrategy {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(QueryMatchStrategy.class);
|
||||
public static final double STEP = 0.1;
|
||||
@Value("${one.detection.size:6}")
|
||||
private Integer oneDetectionSize;
|
||||
@Value("${one.detection.max.size:20}")
|
||||
private Integer oneDetectionMaxSize;
|
||||
@Value("${metric.dimension.threshold:0.3}")
|
||||
private Double metricDimensionThresholdConfig;
|
||||
@Value("${dimension.value.threshold:0.5}")
|
||||
private Double dimensionValueThresholdConfig;
|
||||
|
||||
@Override
|
||||
public List<MapResult> match(String text, List<Term> terms, int retryCount) {
|
||||
return match(text, terms, retryCount, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MapResult> match(String text, List<Term> terms, int retryCount, Integer detectDomainId) {
|
||||
if (CollectionUtils.isEmpty(terms) || StringUtils.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
Map<Integer, Integer> regOffsetToLength = terms.stream().sorted(Comparator.comparing(Term::length))
|
||||
.collect(Collectors.toMap(Term::getOffset, term -> term.word.length(), (value1, value2) -> value2));
|
||||
List<Integer> offsetList = terms.stream().sorted(Comparator.comparing(Term::getOffset))
|
||||
.map(term -> term.getOffset()).collect(Collectors.toList());
|
||||
|
||||
LOGGER.debug("retryCount:{},terms:{},regOffsetToLength:{},offsetList:{},detectDomainId:{}", retryCount, terms,
|
||||
regOffsetToLength, offsetList,
|
||||
detectDomainId);
|
||||
|
||||
return detect(text, regOffsetToLength, offsetList, detectDomainId, retryCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<MatchText, List<MapResult>> matchWithMatchText(String text, List<Term> originals) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<MapResult> detect(String text, Map<Integer, Integer> regOffsetToLength, List<Integer> offsetList,
|
||||
Integer detectDomainId, int retryCount) {
|
||||
List<MapResult> results = Lists.newArrayList();
|
||||
|
||||
for (Integer index = 0; index <= text.length() - 1; ) {
|
||||
|
||||
Set<MapResult> mapResultRowSet = new LinkedHashSet();
|
||||
|
||||
for (Integer i = index; i <= text.length(); ) {
|
||||
int offset = getStepOffset(offsetList, index);
|
||||
i = getStepIndex(regOffsetToLength, i);
|
||||
if (i <= text.length()) {
|
||||
List<MapResult> mapResults = detectByStep(text, detectDomainId, index, i, offset, retryCount);
|
||||
mapResultRowSet.addAll(mapResults);
|
||||
}
|
||||
}
|
||||
|
||||
index = getStepIndex(regOffsetToLength, index);
|
||||
results.addAll(mapResultRowSet);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<MapResult> detectByStep(String text, Integer detectClassId, Integer index, Integer i, int offset,
|
||||
int retryCount) {
|
||||
String detectSegment = text.substring(index, i);
|
||||
// step1. pre search
|
||||
LinkedHashSet<MapResult> mapResults = Suggester.prefixSearch(detectSegment, oneDetectionMaxSize)
|
||||
.stream().collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
// step2. suffix search
|
||||
LinkedHashSet<MapResult> suffixMapResults = Suggester.suffixSearch(detectSegment, oneDetectionMaxSize)
|
||||
.stream().collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
mapResults.addAll(suffixMapResults);
|
||||
|
||||
if (CollectionUtils.isEmpty(mapResults)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
// step3. merge pre/suffix result
|
||||
mapResults = mapResults.stream().sorted((a, b) -> -(b.getName().length() - a.getName().length()))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
// step4. filter by classId
|
||||
if (Objects.nonNull(detectClassId) && detectClassId > 0) {
|
||||
LOGGER.debug("detectDomainId:{}, before parseResults:{}", mapResults);
|
||||
mapResults = mapResults.stream().map(entry -> {
|
||||
List<String> natures = entry.getNatures().stream().filter(
|
||||
nature -> nature.startsWith(NatureType.NATURE_SPILT + detectClassId) || (nature.startsWith(
|
||||
NatureType.NATURE_SPILT))
|
||||
).collect(Collectors.toList());
|
||||
entry.setNatures(natures);
|
||||
return entry;
|
||||
}).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
LOGGER.info("after domainId parseResults:{}", mapResults);
|
||||
}
|
||||
// step5. filter by similarity
|
||||
mapResults = mapResults.stream()
|
||||
.filter(term -> getSimilarity(detectSegment, term.getName()) >= getThresholdMatch(term.getNatures(),
|
||||
retryCount))
|
||||
.filter(term -> CollectionUtils.isNotEmpty(term.getNatures()))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
LOGGER.debug("metricDimensionThreshold:{},dimensionValueThreshold:{},after isSimilarity parseResults:{}",
|
||||
mapResults);
|
||||
|
||||
mapResults = mapResults.stream().map(parseResult -> {
|
||||
parseResult.setOffset(offset);
|
||||
parseResult.setSimilarity(getSimilarity(detectSegment, parseResult.getName()));
|
||||
return parseResult;
|
||||
}).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
// step6. take only one dimension or 10 metric/dimension value per rond.
|
||||
List<MapResult> dimensionMetrics = mapResults.stream().filter(entry -> existDimensionValues(entry.getNatures()))
|
||||
.collect(Collectors.toList())
|
||||
.stream()
|
||||
.limit(1)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isNotEmpty(dimensionMetrics)) {
|
||||
return dimensionMetrics;
|
||||
} else {
|
||||
return mapResults.stream().limit(oneDetectionSize).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getStepIndex(Map<Integer, Integer> regOffsetToLength, Integer index) {
|
||||
Integer subRegLength = regOffsetToLength.get(index);
|
||||
if (Objects.nonNull(subRegLength)) {
|
||||
index = index + subRegLength;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private Integer getStepOffset(List<Integer> termList, Integer index) {
|
||||
for (int j = 0; j < termList.size() - 1; j++) {
|
||||
if (termList.get(j) <= index && termList.get(j + 1) > index) {
|
||||
return termList.get(j);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private double getThresholdMatch(List<String> natures, int retryCount) {
|
||||
if (existDimensionValues(natures)) {
|
||||
return dimensionValueThresholdConfig;
|
||||
}
|
||||
return metricDimensionThresholdConfig - STEP * retryCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.tencent.supersonic.chat.application.mapper;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.hankcs.hanlp.seg.common.Term;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.MatchText;
|
||||
import com.tencent.supersonic.common.nlp.MapResult;
|
||||
import com.tencent.supersonic.common.nlp.NatureType;
|
||||
import com.tencent.supersonic.knowledge.infrastructure.nlp.Suggester;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* match strategy implement
|
||||
*/
|
||||
@Service
|
||||
public class SearchMatchStrategy implements MatchStrategy {
|
||||
|
||||
private static final int SEARCH_SIZE = 3;
|
||||
|
||||
@Override
|
||||
public List<MapResult> match(String text, List<Term> terms, int retryCount) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MapResult> match(String text, List<Term> terms, int retryCount, Integer detectDomainId) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<MatchText, List<MapResult>> matchWithMatchText(String text, List<Term> originals) {
|
||||
|
||||
Map<Integer, Integer> regOffsetToLength = originals.stream()
|
||||
.filter(entry -> !entry.nature.toString().startsWith(NatureType.NATURE_SPILT))
|
||||
.collect(Collectors.toMap(Term::getOffset, value -> value.word.length(),
|
||||
(value1, value2) -> value2));
|
||||
|
||||
List<Integer> detectIndexList = Lists.newArrayList();
|
||||
|
||||
for (Integer index = 0; index < text.length(); ) {
|
||||
|
||||
if (index < text.length()) {
|
||||
detectIndexList.add(index);
|
||||
}
|
||||
Integer regLength = regOffsetToLength.get(index);
|
||||
if (Objects.nonNull(regLength)) {
|
||||
index = index + regLength;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
Map<MatchText, List<MapResult>> regTextMap = new ConcurrentHashMap<>();
|
||||
detectIndexList.stream().parallel().forEach(detectIndex -> {
|
||||
String regText = text.substring(0, detectIndex);
|
||||
String detectSegment = text.substring(detectIndex);
|
||||
|
||||
if (StringUtils.isNotEmpty(detectSegment)) {
|
||||
List<MapResult> mapResults = Suggester.prefixSearch(detectSegment);
|
||||
List<MapResult> suffixMapResults = Suggester.suffixSearch(detectSegment, SEARCH_SIZE);
|
||||
mapResults.addAll(suffixMapResults);
|
||||
// remove entity name where search
|
||||
mapResults = mapResults.stream().filter(entry -> {
|
||||
List<String> natures = entry.getNatures().stream()
|
||||
.filter(nature -> !nature.endsWith(NatureType.ENTITY.getType()))
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(natures)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).collect(Collectors.toList());
|
||||
regTextMap.put(new MatchText(regText, detectSegment), mapResults);
|
||||
}
|
||||
}
|
||||
);
|
||||
return regTextMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.tencent.supersonic.chat.application.parser;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticParser;
|
||||
import com.tencent.supersonic.chat.application.parser.resolver.AggregateTypeResolver;
|
||||
import com.tencent.supersonic.chat.application.query.MetricCompare;
|
||||
import com.tencent.supersonic.chat.application.query.MetricFilter;
|
||||
import com.tencent.supersonic.chat.application.query.MetricGroupBy;
|
||||
import com.tencent.supersonic.chat.application.query.MetricOrderBy;
|
||||
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
|
||||
import com.tencent.supersonic.common.pojo.SchemaItem;
|
||||
import com.tencent.supersonic.common.util.context.ContextUtils;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AggregateSemanticParser implements SemanticParser {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AggregateSemanticParser.class);
|
||||
public static final Integer TOPN_LIMIT = 10;
|
||||
|
||||
private AggregateTypeResolver aggregateTypeResolver;
|
||||
|
||||
@Override
|
||||
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
|
||||
aggregateTypeResolver = ContextUtils.getBean(AggregateTypeResolver.class);
|
||||
|
||||
AggregateTypeEnum aggregateType = aggregateTypeResolver.resolve(queryContext.getQueryText());
|
||||
|
||||
SemanticParseInfo semanticParse = queryContext.getParseInfo();
|
||||
|
||||
List<SchemaItem> metrics = semanticParse.getMetrics();
|
||||
|
||||
semanticParse.setNativeQuery(getNativeQuery(aggregateType, queryContext));
|
||||
|
||||
semanticParse.setAggType(aggregateType);
|
||||
//semanticParse.setOrders(getOrder(aggregateType, metrics));
|
||||
semanticParse.setLimit(Long.valueOf(TOPN_LIMIT));
|
||||
resetQueryModeByAggregateType(queryContext, aggregateType);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* query mode reset by the AggregateType
|
||||
*
|
||||
* @param searchCtx
|
||||
* @param aggregateType
|
||||
*/
|
||||
private void resetQueryModeByAggregateType(QueryContextReq searchCtx, AggregateTypeEnum aggregateType) {
|
||||
|
||||
SemanticParseInfo parseInfo = searchCtx.getParseInfo();
|
||||
String queryMode = parseInfo.getQueryMode();
|
||||
if (MetricGroupBy.QUERY_MODE.equals(queryMode) || MetricGroupBy.QUERY_MODE.equals(queryMode)) {
|
||||
if (AggregateTypeEnum.MAX.equals(aggregateType) || AggregateTypeEnum.MIN.equals(aggregateType)
|
||||
|| AggregateTypeEnum.TOPN.equals(aggregateType)) {
|
||||
parseInfo.setQueryMode(MetricOrderBy.QUERY_MODE);
|
||||
} else {
|
||||
parseInfo.setQueryMode(MetricGroupBy.QUERY_MODE);
|
||||
}
|
||||
}
|
||||
if (MetricFilter.QUERY_MODE.equals(queryMode) || MetricCompare.QUERY_MODE.equals(queryMode)) {
|
||||
if (aggregateTypeResolver.hasCompareIntentionalWords(searchCtx.getQueryText())) {
|
||||
parseInfo.setQueryMode(MetricCompare.QUERY_MODE);
|
||||
} else {
|
||||
parseInfo.setQueryMode(MetricFilter.QUERY_MODE);
|
||||
}
|
||||
}
|
||||
logger.info("queryMode mode [{}]->[{}]", queryMode, parseInfo.getQueryMode());
|
||||
}
|
||||
|
||||
private boolean getNativeQuery(AggregateTypeEnum aggregateType, QueryContextReq searchCtx) {
|
||||
if (AggregateTypeEnum.TOPN.equals(aggregateType)) {
|
||||
return true;
|
||||
}
|
||||
return searchCtx.getParseInfo().getNativeQuery();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.tencent.supersonic.chat.application.parser;
|
||||
|
||||
import static java.time.LocalDate.now;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticParser;
|
||||
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
|
||||
import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver;
|
||||
import com.tencent.supersonic.chat.application.query.EntityDetail;
|
||||
import com.tencent.supersonic.chat.application.query.EntityListFilter;
|
||||
import com.tencent.supersonic.chat.application.query.EntityMetricFilter;
|
||||
import com.tencent.supersonic.chat.application.query.MetricDomain;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.DefaultMetric;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.service.ChatService;
|
||||
import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils;
|
||||
import com.tencent.supersonic.common.pojo.DateConf;
|
||||
import com.tencent.supersonic.common.pojo.SchemaItem;
|
||||
import com.tencent.supersonic.common.util.context.ContextUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DefaultMetricSemanticParser implements SemanticParser {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DefaultMetricSemanticParser.class);
|
||||
private DomainResolver selectStrategy;
|
||||
private ChatService chatService;
|
||||
private DefaultSemanticInternalUtils defaultSemanticUtils;
|
||||
|
||||
@Override
|
||||
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
|
||||
selectStrategy = ContextUtils.getBean(DomainResolver.class);
|
||||
chatService = ContextUtils.getBean(ChatService.class);
|
||||
defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class);
|
||||
String queryMode = queryContext.getParseInfo().getQueryMode();
|
||||
if (StringUtils.isNotEmpty(queryMode)) {
|
||||
// QueryMode Selected
|
||||
if (!EntityListFilter.QUERY_MODE.equals(queryMode)) {
|
||||
Integer domainId = queryContext.getDomainId().intValue();
|
||||
|
||||
List<SchemaElementMatch> matchedElements = queryContext.getMapInfo().getMatchedElements(domainId);
|
||||
if (!CollectionUtils.isEmpty(matchedElements)) {
|
||||
long metricCount = matchedElements.stream()
|
||||
.filter(schemaElementMatch -> schemaElementMatch.getElementType()
|
||||
.equals(SchemaElementType.METRIC)).count();
|
||||
if (metricCount <= 0) {
|
||||
if (chatCtx.getParseInfo() == null
|
||||
|| chatCtx.getParseInfo().getMetrics() == null
|
||||
|| chatCtx.getParseInfo().getMetrics().size() <= 0) {
|
||||
logger.info("fillThemeDefaultMetricLogic");
|
||||
fillThemeDefaultMetricLogic(queryContext.getParseInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
fillDateDomain(chatCtx, queryContext);
|
||||
}
|
||||
}
|
||||
defaultQueryMode(queryContext, chatCtx);
|
||||
|
||||
if (EntityDetail.QUERY_MODE.equals(queryMode) || EntityMetricFilter.QUERY_MODE.equals(queryMode)) {
|
||||
addEntityDetailDimensionMetric(queryContext, chatCtx);
|
||||
dealNativeQuery(queryContext, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void dealNativeQuery(QueryContextReq queryContext, boolean isNativeQuery) {
|
||||
if (Objects.nonNull(queryContext) && Objects.nonNull(queryContext.getParseInfo())) {
|
||||
queryContext.getParseInfo().setNativeQuery(isNativeQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> addPrimaryDimension(EntityRichInfo entity, List<SchemaItem> dimensions) {
|
||||
Set<String> primaryDimensions = new HashSet<>();
|
||||
if (Objects.isNull(entity) || CollectionUtils.isEmpty(entity.getEntityIds())) {
|
||||
return primaryDimensions;
|
||||
}
|
||||
entity.getEntityIds().stream().forEach(dimSchemaDesc -> {
|
||||
SchemaItem dimension = new SchemaItem();
|
||||
BeanUtils.copyProperties(dimSchemaDesc, dimension);
|
||||
dimensions.add(dimension);
|
||||
primaryDimensions.add(dimSchemaDesc.getBizName());
|
||||
});
|
||||
return primaryDimensions;
|
||||
}
|
||||
|
||||
protected void addEntityDetailDimensionMetric(QueryContextReq searchCtx, ChatContext chatCtx) {
|
||||
if (searchCtx.getParseInfo().getDomainId() > 0) {
|
||||
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(
|
||||
searchCtx.getParseInfo().getDomainId());
|
||||
if (chaConfigRichDesc != null) {
|
||||
SemanticParseInfo semanticParseInfo = searchCtx.getParseInfo();
|
||||
if (Objects.nonNull(semanticParseInfo) && CollectionUtils.isEmpty(semanticParseInfo.getDimensions())) {
|
||||
List<SchemaItem> dimensions = new ArrayList<>();
|
||||
List<SchemaItem> metrics = new ArrayList<>();
|
||||
if (chaConfigRichDesc.getEntity() != null
|
||||
&& chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) {
|
||||
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream()
|
||||
.forEach(m -> metrics.add(getMetric(m)));
|
||||
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getDimensionList().stream()
|
||||
.forEach(m -> dimensions.add(getDimension(m)));
|
||||
}
|
||||
semanticParseInfo.setDimensions(dimensions);
|
||||
semanticParseInfo.setMetrics(metrics);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void defaultQueryMode(QueryContextReq searchCtx, ChatContext chatCtx) {
|
||||
SchemaMapInfo schemaMap = searchCtx.getMapInfo();
|
||||
SemanticParseInfo parseInfo = searchCtx.getParseInfo();
|
||||
if (StringUtils.isEmpty(parseInfo.getQueryMode())) {
|
||||
if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getDomainId() > 0) {
|
||||
//
|
||||
Long domain = chatCtx.getParseInfo().getDomainId();
|
||||
String queryMode = chatCtx.getParseInfo().getQueryMode();
|
||||
if (!CollectionUtils.isEmpty(schemaMap.getMatchedDomains()) && schemaMap.getMatchedDomains()
|
||||
.contains(domain.intValue())) {
|
||||
List<SchemaElementMatch> elementMatches = schemaMap.getMatchedElements(domain.intValue());
|
||||
Long filterNUm = elementMatches.stream()
|
||||
.filter(e -> e.getElementType().equals(SchemaElementType.VALUE) || e.getElementType()
|
||||
.equals(SchemaElementType.ID)).count();
|
||||
Long dimensionNUm = elementMatches.stream()
|
||||
.filter(e -> e.getElementType().equals(SchemaElementType.DIMENSION)).count();
|
||||
Long metricrNUm = elementMatches.stream()
|
||||
.filter(e -> e.getElementType().equals(SchemaElementType.METRIC)).count();
|
||||
if (filterNUm > 0 && dimensionNUm > 0 && metricrNUm > 0) {
|
||||
// default as entity detail queryMode
|
||||
logger.info("defaultQueryMode [{}]", EntityDetail.QUERY_MODE);
|
||||
parseInfo.setQueryMode(EntityDetail.QUERY_MODE);
|
||||
parseInfo.setDomainId(domain);
|
||||
return;
|
||||
}
|
||||
Long entityNUm = elementMatches.stream()
|
||||
.filter(e -> e.getElementType().equals(SchemaElementType.ENTITY)).count();
|
||||
if (filterNUm <= 0 && dimensionNUm <= 0 && entityNUm <= 0) {
|
||||
// default as metric domain
|
||||
if (metricrNUm > 0 || MetricDomain.QUERY_MODE.equals(queryMode)) {
|
||||
// default as entity detail queryMode
|
||||
logger.info("defaultQueryMode [{}]", MetricDomain.QUERY_MODE);
|
||||
parseInfo.setQueryMode(MetricDomain.QUERY_MODE);
|
||||
parseInfo.setDomainId(domain);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isEmpty(schemaMap.getMatchedDomains()) && parseInfo != null
|
||||
&& parseInfo.getDateInfo() != null) {
|
||||
// only query time
|
||||
if (MetricDomain.QUERY_MODE.equals(queryMode)) {
|
||||
// METRIC_DOMAIN context
|
||||
logger.info("defaultQueryMode [{}]", MetricDomain.QUERY_MODE);
|
||||
parseInfo.setQueryMode(MetricDomain.QUERY_MODE);
|
||||
parseInfo.setDomainId(domain);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void fillDateDomain(ChatContext chatCtx, QueryContextReq searchCtx) {
|
||||
SemanticParseInfo parseInfo = searchCtx.getParseInfo();
|
||||
|
||||
if (parseInfo == null || parseInfo.getDateInfo() == null) {
|
||||
boolean isUpdateTime = false;
|
||||
if (selectStrategy.isDomainSwitch(chatCtx, searchCtx)) {
|
||||
isUpdateTime = true;
|
||||
}
|
||||
if (chatCtx.getParseInfo() == null
|
||||
|| chatCtx.getParseInfo().getDateInfo() == null) {
|
||||
isUpdateTime = true;
|
||||
}
|
||||
if (isUpdateTime && parseInfo != null && parseInfo.getDomainId() > 0) {
|
||||
logger.info("fillThemeDefaultTime");
|
||||
fillThemeDefaultTime(parseInfo.getDomainId(), parseInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillThemeDefaultMetricLogic(SemanticParseInfo semanticParseInfo) {
|
||||
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(
|
||||
semanticParseInfo.getDomainId());
|
||||
|
||||
if (Objects.isNull(chaConfigRichDesc) || CollectionUtils.isEmpty(chaConfigRichDesc.getDefaultMetrics())) {
|
||||
log.info("there is no defaultMetricIds info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(semanticParseInfo.getMetrics()) && CollectionUtils.isEmpty(
|
||||
semanticParseInfo.getDimensions())) {
|
||||
List<SchemaItem> metrics = new ArrayList<>();
|
||||
chaConfigRichDesc.getDefaultMetrics().stream().forEach(metric -> {
|
||||
SchemaItem metricTmp = new SchemaItem();
|
||||
metricTmp.setId(metric.getMetricId());
|
||||
metricTmp.setBizName(metric.getBizName());
|
||||
metrics.add(metricTmp);
|
||||
});
|
||||
semanticParseInfo.setMetrics(metrics);
|
||||
}
|
||||
|
||||
if (Objects.isNull(semanticParseInfo.getDateInfo()) || Objects.isNull(
|
||||
semanticParseInfo.getDateInfo().getDateMode())) {
|
||||
DefaultMetric defaultMetricInfo = chaConfigRichDesc.getDefaultMetrics().get(0);
|
||||
DateConf dateInfo = new DateConf();
|
||||
dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS);
|
||||
dateInfo.setUnit(defaultMetricInfo.getUnit());
|
||||
dateInfo.setPeriod(defaultMetricInfo.getPeriod());
|
||||
semanticParseInfo.setDateInfo(dateInfo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void fillThemeDefaultTime(Long domain, SemanticParseInfo semanticParseInfo) {
|
||||
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(
|
||||
semanticParseInfo.getDomainId());
|
||||
if (!Objects.isNull(chaConfigRichDesc) && !CollectionUtils.isEmpty(chaConfigRichDesc.getDefaultMetrics())) {
|
||||
DefaultMetric defaultMetricInfo = chaConfigRichDesc.getDefaultMetrics().get(0);
|
||||
DateConf dateInfo = new DateConf();
|
||||
dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS);
|
||||
dateInfo.setUnit(defaultMetricInfo.getUnit());
|
||||
dateInfo.setStartDate(now().minusDays(defaultMetricInfo.getUnit()).toString());
|
||||
dateInfo.setEndDate(now().minusDays(1).toString());
|
||||
dateInfo.setPeriod(defaultMetricInfo.getPeriod());
|
||||
semanticParseInfo.setDateInfo(dateInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private SchemaItem getMetric(MetricSchemaResp metricSchemaDesc) {
|
||||
SchemaItem queryMeta = new SchemaItem();
|
||||
queryMeta.setId(metricSchemaDesc.getId());
|
||||
queryMeta.setBizName(metricSchemaDesc.getBizName());
|
||||
queryMeta.setName(metricSchemaDesc.getName());
|
||||
return queryMeta;
|
||||
}
|
||||
|
||||
private SchemaItem getDimension(DimSchemaResp dimSchemaDesc) {
|
||||
SchemaItem queryMeta = new SchemaItem();
|
||||
queryMeta.setId(dimSchemaDesc.getId());
|
||||
queryMeta.setBizName(dimSchemaDesc.getBizName());
|
||||
queryMeta.setName(dimSchemaDesc.getName());
|
||||
return queryMeta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.tencent.supersonic.chat.application.parser;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticLayer;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticParser;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticQuery;
|
||||
import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver;
|
||||
import com.tencent.supersonic.chat.application.parser.resolver.SemanticQueryResolver;
|
||||
import com.tencent.supersonic.chat.domain.pojo.semantic.DomainInfos;
|
||||
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
|
||||
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
|
||||
import com.tencent.supersonic.common.util.context.ContextUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Component
|
||||
public class DomainSemanticParser implements SemanticParser {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DomainSemanticParser.class);
|
||||
private DomainResolver domainResolver;
|
||||
|
||||
private SemanticQueryResolver semanticQueryResolver;
|
||||
|
||||
@Override
|
||||
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
|
||||
DomainInfos domainInfosDb = SchemaInfoConverter.convert(
|
||||
ContextUtils.getBean(SemanticLayer.class).getDomainSchemaInfo(new ArrayList<>()));
|
||||
Map<Integer, String> domainToName = domainInfosDb.getDomainToName();
|
||||
|
||||
SchemaMapInfo mapInfo = queryContext.getMapInfo();
|
||||
SemanticParseInfo parseInfo = queryContext.getParseInfo();
|
||||
|
||||
domainResolver = ContextUtils.getBean(DomainResolver.class);
|
||||
semanticQueryResolver = ContextUtils.getBean(SemanticQueryResolver.class);
|
||||
|
||||
Map<Integer, SemanticQuery> domainSemanticQuery = new HashMap<>();
|
||||
// Round 1: find all domains that can be resolved to any query mode
|
||||
|
||||
for (Integer domain : mapInfo.getMatchedDomains()) {
|
||||
List<SchemaElementMatch> elementMatches = mapInfo.getMatchedElements(domain);
|
||||
|
||||
SemanticQuery query = semanticQueryResolver.resolve(elementMatches, queryContext);
|
||||
|
||||
if (Objects.nonNull(query)) {
|
||||
domainSemanticQuery.put(domain, query);
|
||||
}
|
||||
}
|
||||
// only one domain is found, no need to rank
|
||||
if (domainSemanticQuery.size() == 1) {
|
||||
Optional<Map.Entry<Integer, SemanticQuery>> match = domainSemanticQuery.entrySet().stream().findFirst();
|
||||
if (match.isPresent()) {
|
||||
logger.info("select by only one [{}:{}]", match.get().getKey(), match.get().getValue());
|
||||
parseInfo.setDomainId(Long.valueOf(match.get().getKey()));
|
||||
parseInfo.setDomainName(domainToName.get(Integer.valueOf(match.get().getKey())));
|
||||
parseInfo.setQueryMode(match.get().getValue().getQueryMode());
|
||||
return false;
|
||||
}
|
||||
} else if (domainSemanticQuery.size() > 1) {
|
||||
// will choose one by the domain select
|
||||
Integer domainId = domainResolver.resolve(domainSemanticQuery, queryContext, chatCtx, mapInfo);
|
||||
if (domainId > 0) {
|
||||
Map.Entry<Integer, SemanticQuery> match = domainSemanticQuery.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().equals(domainId)).findFirst().orElse(null);
|
||||
logger.info("select by selectStrategy [{}:{}]", domainId, match.getValue());
|
||||
parseInfo.setDomainId(Long.valueOf(match.getKey()));
|
||||
parseInfo.setDomainName(domainToName.get(Integer.valueOf(match.getKey())));
|
||||
parseInfo.setQueryMode(match.getValue().getQueryMode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Round 2: no domains can be found yet, count in chat context
|
||||
if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getDomainId() > 0) {
|
||||
Integer chatDomain = Integer.valueOf(chatCtx.getParseInfo().getDomainId().intValue());
|
||||
if (mapInfo.getMatchedDomains().contains(chatDomain) || CollectionUtils.isEmpty(
|
||||
mapInfo.getMatchedDomains())) {
|
||||
List<SchemaElementMatch> elementMatches = mapInfo.getMatchedElements(chatDomain);
|
||||
if (CollectionUtils.isEmpty(elementMatches)) {
|
||||
parseInfo.setDomainId(Long.valueOf(chatDomain));
|
||||
parseInfo.setDomainName(domainToName.get(chatDomain));
|
||||
parseInfo.setQueryMode(chatCtx.getParseInfo().getQueryMode());
|
||||
return false;
|
||||
}
|
||||
|
||||
SemanticQuery query = tryParseByContext(elementMatches, chatCtx, queryContext);
|
||||
if (Objects.nonNull(query)) {
|
||||
logger.info("select by context count [{}:{}]", chatDomain, query);
|
||||
parseInfo.setDomainId(Long.valueOf(chatDomain));
|
||||
parseInfo.setDomainName(domainToName.get(chatDomain));
|
||||
parseInfo.setQueryMode(query.getQueryMode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Round 3: no domains can be found yet, count in default metric
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* try to add ChatContext to SchemaElementMatch and look if match QueryMode
|
||||
*
|
||||
* @param elementMatches
|
||||
* @param chatCtx
|
||||
* @return
|
||||
*/
|
||||
private SemanticQuery tryParseByContext(List<SchemaElementMatch> elementMatches, ChatContext chatCtx,
|
||||
QueryContextReq searchCt) {
|
||||
if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getEntity() > 0) {
|
||||
Long entityCount = elementMatches.stream().filter(i -> SchemaElementType.ENTITY.equals(i.getElementType()))
|
||||
.count();
|
||||
Long metricCount = elementMatches.stream().filter(i -> SchemaElementType.METRIC.equals(i.getElementType()))
|
||||
.count();
|
||||
if (entityCount <= 0 && metricCount <= 0 && ContextHelper.hasEntityId(chatCtx)) {
|
||||
// try entity parse
|
||||
SchemaElementMatch entityElementMatch = new SchemaElementMatch();
|
||||
entityElementMatch.setElementType(SchemaElementType.ENTITY);
|
||||
List<SchemaElementMatch> newSchemaElementMatch = new ArrayList<>();
|
||||
if (!CollectionUtils.isEmpty(elementMatches)) {
|
||||
newSchemaElementMatch.addAll(elementMatches);
|
||||
}
|
||||
newSchemaElementMatch.add(entityElementMatch);
|
||||
SemanticQuery semanticQuery = doParseByContext(newSchemaElementMatch, chatCtx, searchCt);
|
||||
if (Objects.nonNull(semanticQuery)) {
|
||||
return semanticQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
return doParseByContext(elementMatches, chatCtx, searchCt);
|
||||
}
|
||||
|
||||
private SemanticQuery doParseByContext(List<SchemaElementMatch> elementMatches, ChatContext chatCtx,
|
||||
QueryContextReq searchCt) {
|
||||
SemanticParseInfo contextSemanticParseInfo = chatCtx.getParseInfo();
|
||||
if (contextSemanticParseInfo != null) {
|
||||
List<SchemaElementMatch> newSchemaElementMatch = new ArrayList<>();
|
||||
List<List<SchemaElementType>> trySchemaElementTypes = new LinkedList<>();
|
||||
// try DIMENSION+METRIC+VALUE
|
||||
// try DIMENSION+METRIC METRIC+VALUE DIMENSION+VALUE
|
||||
// try DIMENSION METRIC VALUE single
|
||||
trySchemaElementTypes.add(new ArrayList<>(
|
||||
Arrays.asList(SchemaElementType.DIMENSION, SchemaElementType.METRIC, SchemaElementType.VALUE)));
|
||||
trySchemaElementTypes.add(
|
||||
new ArrayList<>(Arrays.asList(SchemaElementType.METRIC, SchemaElementType.VALUE)));
|
||||
trySchemaElementTypes.add(
|
||||
new ArrayList<>(Arrays.asList(SchemaElementType.DIMENSION, SchemaElementType.METRIC)));
|
||||
trySchemaElementTypes.add(
|
||||
new ArrayList<>(Arrays.asList(SchemaElementType.DIMENSION, SchemaElementType.VALUE)));
|
||||
trySchemaElementTypes.add(new ArrayList<>(Arrays.asList(SchemaElementType.METRIC)));
|
||||
trySchemaElementTypes.add(new ArrayList<>(Arrays.asList(SchemaElementType.VALUE)));
|
||||
trySchemaElementTypes.add(new ArrayList<>(Arrays.asList(SchemaElementType.DIMENSION)));
|
||||
|
||||
for (List<SchemaElementType> schemaElementTypes : trySchemaElementTypes) {
|
||||
newSchemaElementMatch.clear();
|
||||
if (!CollectionUtils.isEmpty(elementMatches)) {
|
||||
newSchemaElementMatch.addAll(elementMatches);
|
||||
}
|
||||
ContextHelper.mergeContextSchemaElementMatch(newSchemaElementMatch, elementMatches, schemaElementTypes,
|
||||
contextSemanticParseInfo);
|
||||
SemanticQuery semanticQuery = semanticQueryResolver.resolve(newSchemaElementMatch, searchCt);
|
||||
if (semanticQuery != null) {
|
||||
return semanticQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.tencent.supersonic.chat.application.parser;
|
||||
|
||||
import static com.tencent.supersonic.common.constant.Constants.DAY;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticParser;
|
||||
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
|
||||
import com.tencent.supersonic.chat.application.query.EntityListFilter;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils;
|
||||
import com.tencent.supersonic.common.constant.Constants;
|
||||
import com.tencent.supersonic.common.pojo.DateConf;
|
||||
import com.tencent.supersonic.common.pojo.Order;
|
||||
import com.tencent.supersonic.common.pojo.SchemaItem;
|
||||
import com.tencent.supersonic.common.util.context.ContextUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Component
|
||||
public class ListFilterParser implements SemanticParser {
|
||||
|
||||
|
||||
private DefaultSemanticInternalUtils defaultSemanticUtils;
|
||||
|
||||
@Override
|
||||
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
|
||||
defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class);
|
||||
|
||||
String queryMode = queryContext.getParseInfo().getQueryMode();
|
||||
if (!EntityListFilter.QUERY_MODE.equals(queryMode)) {
|
||||
return false;
|
||||
}
|
||||
this.fillDateEntityFilter(queryContext.getParseInfo());
|
||||
this.addEntityDetailAndOrderByMetric(queryContext, chatCtx);
|
||||
this.dealNativeQuery(queryContext, true);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private void fillDateEntityFilter(SemanticParseInfo semanticParseInfo) {
|
||||
DateConf dateInfo = new DateConf();
|
||||
dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS);
|
||||
dateInfo.setUnit(1);
|
||||
dateInfo.setPeriod(DAY);
|
||||
dateInfo.setText(String.format("近1天"));
|
||||
semanticParseInfo.setDateInfo(dateInfo);
|
||||
}
|
||||
|
||||
private void addEntityDetailAndOrderByMetric(QueryContextReq searchCtx, ChatContext chatCtx) {
|
||||
if (searchCtx.getParseInfo().getDomainId() > 0L) {
|
||||
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(
|
||||
searchCtx.getParseInfo().getDomainId());
|
||||
if (chaConfigRichDesc != null) {
|
||||
SemanticParseInfo semanticParseInfo = searchCtx.getParseInfo();
|
||||
List<SchemaItem> dimensions = new ArrayList();
|
||||
Set<String> primaryDimensions = this.addPrimaryDimension(chaConfigRichDesc.getEntity(), dimensions);
|
||||
List<SchemaItem> metrics = new ArrayList();
|
||||
if (chaConfigRichDesc.getEntity() != null
|
||||
&& chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) {
|
||||
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream()
|
||||
.forEach((m) -> metrics.add(this.getMetric(m)));
|
||||
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getDimensionList().stream()
|
||||
.filter((m) -> !primaryDimensions.contains(m.getBizName()))
|
||||
.forEach((m) -> dimensions.add(this.getDimension(m)));
|
||||
}
|
||||
|
||||
semanticParseInfo.setDimensions(dimensions);
|
||||
semanticParseInfo.setMetrics(metrics);
|
||||
List<Order> orders = new ArrayList();
|
||||
if (chaConfigRichDesc.getEntity() != null
|
||||
&& chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) {
|
||||
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream()
|
||||
.forEach((metric) -> orders.add(new Order(metric.getBizName(), Constants.DESC_UPPER)));
|
||||
}
|
||||
|
||||
semanticParseInfo.setOrders(orders);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Set<String> addPrimaryDimension(EntityRichInfo entity, List<SchemaItem> dimensions) {
|
||||
Set<String> primaryDimensions = new HashSet();
|
||||
if (!Objects.isNull(entity) && !CollectionUtils.isEmpty(entity.getEntityIds())) {
|
||||
entity.getEntityIds().stream().forEach((dimSchemaDesc) -> {
|
||||
SchemaItem dimension = new SchemaItem();
|
||||
BeanUtils.copyProperties(dimSchemaDesc, dimension);
|
||||
dimensions.add(dimension);
|
||||
primaryDimensions.add(dimSchemaDesc.getBizName());
|
||||
});
|
||||
return primaryDimensions;
|
||||
} else {
|
||||
return primaryDimensions;
|
||||
}
|
||||
}
|
||||
|
||||
private SchemaItem getMetric(MetricSchemaResp metricSchemaDesc) {
|
||||
SchemaItem queryMeta = new SchemaItem();
|
||||
queryMeta.setId(metricSchemaDesc.getId());
|
||||
queryMeta.setBizName(metricSchemaDesc.getBizName());
|
||||
queryMeta.setName(metricSchemaDesc.getName());
|
||||
return queryMeta;
|
||||
}
|
||||
|
||||
private SchemaItem getDimension(DimSchemaResp dimSchemaDesc) {
|
||||
SchemaItem queryMeta = new SchemaItem();
|
||||
queryMeta.setId(dimSchemaDesc.getId());
|
||||
queryMeta.setBizName(dimSchemaDesc.getBizName());
|
||||
queryMeta.setName(dimSchemaDesc.getName());
|
||||
return queryMeta;
|
||||
}
|
||||
|
||||
private void dealNativeQuery(QueryContextReq searchCtx, boolean isNativeQuery) {
|
||||
if (Objects.nonNull(searchCtx) && Objects.nonNull(searchCtx.getParseInfo())) {
|
||||
searchCtx.getParseInfo().setNativeQuery(isNativeQuery);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.tencent.supersonic.chat.application.parser;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticParser;
|
||||
import com.tencent.supersonic.common.constant.Constants;
|
||||
import com.tencent.supersonic.common.pojo.DateConf;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TimeSemanticParser implements SemanticParser {
|
||||
|
||||
public TimeSemanticParser() {
|
||||
}
|
||||
|
||||
private int zhNumParse(String zhNumStr) {
|
||||
Stack<Integer> stack = new Stack<>();
|
||||
String numStr = "一二三四五六七八九";
|
||||
String unitStr = "十百千万亿";
|
||||
|
||||
String[] ssArr = zhNumStr.split("");
|
||||
for (String e : ssArr) {
|
||||
int numIndex = numStr.indexOf(e);
|
||||
int unitIndex = unitStr.indexOf(e);
|
||||
if (numIndex != -1) {
|
||||
stack.push(numIndex + 1);
|
||||
} else if (unitIndex != -1) {
|
||||
int unitNum = (int) Math.pow(10, unitIndex + 1);
|
||||
if (stack.isEmpty()) {
|
||||
stack.push(unitNum);
|
||||
} else {
|
||||
stack.push(stack.pop() * unitNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack.stream().mapToInt(s -> s).sum();
|
||||
}
|
||||
|
||||
private static final Pattern recentPeriodPattern = Pattern.compile(
|
||||
".*(?<periodStr>(近|过去)((?<enNum>\\d+)|(?<zhNum>[一二三四五六七八九十百千万亿]+))个?(?<zhPeriod>[天周月年])).*");
|
||||
|
||||
@Override
|
||||
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
|
||||
Matcher m = recentPeriodPattern.matcher(queryContext.getQueryText());
|
||||
if (m.matches()) {
|
||||
int num = 0;
|
||||
String enNum = m.group("enNum");
|
||||
String zhNum = m.group("zhNum");
|
||||
if (enNum != null) {
|
||||
num = Integer.parseInt(enNum);
|
||||
} else if (zhNum != null) {
|
||||
num = zhNumParse(zhNum);
|
||||
}
|
||||
if (num > 0) {
|
||||
DateConf info = new DateConf();
|
||||
String zhPeriod = m.group("zhPeriod");
|
||||
int days;
|
||||
switch (zhPeriod) {
|
||||
case "周":
|
||||
days = 7;
|
||||
info.setPeriod(Constants.WEEK);
|
||||
break;
|
||||
case "月":
|
||||
days = 30;
|
||||
info.setPeriod(Constants.MONTH);
|
||||
break;
|
||||
case "年":
|
||||
days = 365;
|
||||
info.setPeriod(Constants.YEAR);
|
||||
break;
|
||||
default:
|
||||
days = 1;
|
||||
info.setPeriod(Constants.DAY);
|
||||
}
|
||||
days = days * num;
|
||||
info.setDateMode(DateConf.DateMode.RECENT_UNITS);
|
||||
String text = "近" + num + zhPeriod;
|
||||
if (Strings.isNotEmpty(m.group("periodStr"))) {
|
||||
text = m.group("periodStr");
|
||||
}
|
||||
info.setText(text);
|
||||
info.setStartDate(LocalDate.now().minusDays(days).toString());
|
||||
info.setUnit(days);
|
||||
queryContext.getParseInfo().setDateInfo(info);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.tencent.supersonic.chat.application.parser.resolver;
|
||||
|
||||
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
|
||||
|
||||
/***
|
||||
* aggregate parser
|
||||
*/
|
||||
public interface AggregateTypeResolver {
|
||||
|
||||
AggregateTypeEnum resolve(String queryText);
|
||||
|
||||
boolean hasCompareIntentionalWords(String queryText);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.tencent.supersonic.chat.application.parser.resolver;
|
||||
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticQuery;
|
||||
import java.util.Map;
|
||||
|
||||
public interface DomainResolver {
|
||||
|
||||
Integer resolve(Map<Integer, SemanticQuery> domainQueryModes, QueryContextReq queryCtx, ChatContext chatCtx,
|
||||
SchemaMapInfo schemaMap);
|
||||
|
||||
boolean isDomainSwitch(ChatContext chatCtx, QueryContextReq queryCtx);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.tencent.supersonic.chat.application.parser.resolver;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticQuery;
|
||||
import com.tencent.supersonic.chat.application.query.SemanticQueryFactory;
|
||||
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class DomainSemanticQueryResolver implements SemanticQueryResolver {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DomainSemanticQueryResolver.class);
|
||||
|
||||
@Override
|
||||
public SemanticQuery resolve(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx) {
|
||||
Map<SemanticQuery, SchemaElementCount> matchMap = new HashMap<>();
|
||||
|
||||
for (SemanticQuery semanticQuery : SemanticQueryFactory.getSemanticQueries()) {
|
||||
|
||||
SchemaElementCount match = semanticQuery.match(elementMatches, queryCtx);
|
||||
|
||||
if (match != null && match.getCount() > 0 && match.getMaxSimilarity() > 0) {
|
||||
LOGGER.info("resolve match [{}:{}] ", semanticQuery.getQueryMode(), match);
|
||||
matchMap.put(semanticQuery, match);
|
||||
}
|
||||
|
||||
}
|
||||
// get the similarity max
|
||||
Map.Entry<SemanticQuery, SchemaElementCount> matchMax = matchMap.entrySet().stream()
|
||||
.sorted(ContextHelper.SemanticQueryStatComparator).findFirst().orElse(null);
|
||||
if (matchMax != null) {
|
||||
LOGGER.info("resolve max [{}:{}] ", matchMax.getKey().getQueryMode(), matchMax.getValue());
|
||||
return matchMax.getKey();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.tencent.supersonic.chat.application.parser.resolver;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticQuery;
|
||||
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Service("heuristicDomainResolver")
|
||||
@Primary
|
||||
public class HeuristicDomainResolver implements DomainResolver {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HeuristicDomainResolver.class);
|
||||
|
||||
@Override
|
||||
public Integer resolve(Map<Integer, SemanticQuery> domainQueryModes, QueryContextReq searchCtx,
|
||||
ChatContext chatCtx, SchemaMapInfo schemaMap) {
|
||||
// if QueryContext has domainId and in domainQueryModes
|
||||
if (domainQueryModes.containsKey(searchCtx.getDomainId())) {
|
||||
LOGGER.info("selectDomain from QueryContext [{}]", searchCtx.getDomainId());
|
||||
return searchCtx.getDomainId();
|
||||
}
|
||||
// if ChatContext has domainId and in domainQueryModes
|
||||
if (chatCtx.getParseInfo().getDomainId() > 0) {
|
||||
Integer domainId = Integer.valueOf(chatCtx.getParseInfo().getDomainId().intValue());
|
||||
if (!isAllowSwitch(domainQueryModes, schemaMap, chatCtx, searchCtx, domainId)) {
|
||||
LOGGER.info("selectDomain from ChatContext [{}]", domainId);
|
||||
return domainId;
|
||||
}
|
||||
}
|
||||
// get the max SchemaElementType number
|
||||
Map<Integer, SchemaElementCount> domainTypeMap = getDomainTypeMap(schemaMap);
|
||||
if (domainTypeMap.size() == 1) {
|
||||
Integer domainSelect = domainTypeMap.entrySet().stream().collect(Collectors.toList()).get(0).getKey();
|
||||
if (domainQueryModes.containsKey(domainSelect)) {
|
||||
LOGGER.info("selectDomain from domainTypeMap not order [{}]", domainSelect);
|
||||
return domainSelect;
|
||||
}
|
||||
} else {
|
||||
Map.Entry<Integer, SchemaElementCount> maxDomain = domainTypeMap.entrySet().stream()
|
||||
.filter(entry -> domainQueryModes.containsKey(entry.getKey()))
|
||||
.sorted(ContextHelper.DomainStatComparator).findFirst().orElse(null);
|
||||
if (maxDomain != null) {
|
||||
LOGGER.info("selectDomain from domainTypeMap need order [{}]", maxDomain.getKey());
|
||||
return maxDomain.getKey();
|
||||
}
|
||||
}
|
||||
// bad case , here will not reach , default 0
|
||||
LOGGER.error("selectDomain not found ");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDomainSwitch(ChatContext chatCtx, QueryContextReq searchCtx) {
|
||||
Long contextDomain = chatCtx.getParseInfo().getDomainId();
|
||||
Long currentDomain = searchCtx.getParseInfo().getDomainId();
|
||||
boolean noSwitch = currentDomain == null || contextDomain == null || contextDomain.equals(currentDomain);
|
||||
LOGGER.info("ChatContext isDomainSwitch [{}]", !noSwitch);
|
||||
return !noSwitch;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* to check can switch domain if context exit domain
|
||||
*
|
||||
* @return false will use context domain, true will use other domain , maybe include context domain
|
||||
*/
|
||||
private static boolean isAllowSwitch(Map<Integer, SemanticQuery> domainQueryModes, SchemaMapInfo schemaMap,
|
||||
ChatContext chatCtx, QueryContextReq searchCtx, Integer domainId) {
|
||||
if (!Objects.nonNull(domainId) || domainId <= 0) {
|
||||
return true;
|
||||
}
|
||||
// except content domain, calculate the number of types for each domain, if numbers<=1 will not switch
|
||||
Map<Integer, SchemaElementCount> domainTypeMap = getDomainTypeMap(schemaMap);
|
||||
LOGGER.info("isAllowSwitch domainTypeMap [{}]", domainTypeMap);
|
||||
long otherDomainTypeNumBigOneCount = domainTypeMap.entrySet().stream()
|
||||
.filter(entry -> domainQueryModes.containsKey(entry.getKey()) && !entry.getKey().equals(domainId))
|
||||
.filter(entry -> entry.getValue().getCount() > 1).count();
|
||||
if (otherDomainTypeNumBigOneCount >= 1) {
|
||||
return true;
|
||||
}
|
||||
// if query text only contain time , will not switch
|
||||
if (searchCtx.getQueryText() != null && searchCtx.getParseInfo().getDateInfo() != null) {
|
||||
if (searchCtx.getParseInfo().getDateInfo().getText() != null) {
|
||||
if (searchCtx.getParseInfo().getDateInfo().getText().equalsIgnoreCase(searchCtx.getQueryText())) {
|
||||
LOGGER.info("timeParseResults is not null , can not switch context , timeParseResults:{},",
|
||||
searchCtx.getParseInfo().getDateInfo());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if context domain not in schemaMap , will switch
|
||||
if (schemaMap.getMatchedElements(domainId) == null || schemaMap.getMatchedElements(domainId).size() <= 0) {
|
||||
LOGGER.info("domainId not in schemaMap ");
|
||||
return true;
|
||||
}
|
||||
// other will not switch
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Map<Integer, SchemaElementCount> getDomainTypeMap(SchemaMapInfo schemaMap) {
|
||||
Map<Integer, SchemaElementCount> domainCount = new HashMap<>();
|
||||
for (Map.Entry<Integer, List<SchemaElementMatch>> entry : schemaMap.getDomainElementMatches().entrySet()) {
|
||||
List<SchemaElementMatch> schemaElementMatches = schemaMap.getMatchedElements(entry.getKey());
|
||||
if (schemaElementMatches != null && schemaElementMatches.size() > 0) {
|
||||
if (!domainCount.containsKey(entry.getKey())) {
|
||||
domainCount.put(entry.getKey(), new SchemaElementCount());
|
||||
}
|
||||
SchemaElementCount schemaElementCount = domainCount.get(entry.getKey());
|
||||
Set<SchemaElementType> schemaElementTypes = new HashSet<>();
|
||||
schemaElementMatches.stream()
|
||||
.forEach(schemaElementMatch -> schemaElementTypes.add(schemaElementMatch.getElementType()));
|
||||
SchemaElementMatch schemaElementMatchMax = schemaElementMatches.stream()
|
||||
.sorted(ContextHelper.schemaElementMatchComparatorBySimilarity).findFirst().orElse(null);
|
||||
if (schemaElementMatchMax != null) {
|
||||
schemaElementCount.setMaxSimilarity(schemaElementMatchMax.getSimilarity());
|
||||
}
|
||||
schemaElementCount.setCount(schemaElementTypes.size());
|
||||
|
||||
}
|
||||
}
|
||||
return domainCount;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.tencent.supersonic.chat.application.parser.resolver;
|
||||
|
||||
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@Primary
|
||||
public class RegexAggregateTypeResolver implements AggregateTypeResolver {
|
||||
|
||||
private static Map<AggregateTypeEnum, Pattern> aggregateRegexMap = new HashMap<>();
|
||||
private static Pattern compareIntentionalWord = Pattern.compile("(?i)(比较|对比)");
|
||||
|
||||
static {
|
||||
aggregateRegexMap.put(AggregateTypeEnum.MAX, Pattern.compile("(?i)(最大值|最大|max|峰值|最高)"));
|
||||
aggregateRegexMap.put(AggregateTypeEnum.MIN, Pattern.compile("(?i)(最小值|最小|min|最低)"));
|
||||
aggregateRegexMap.put(AggregateTypeEnum.SUM, Pattern.compile("(?i)(汇总|总和|sum)"));
|
||||
aggregateRegexMap.put(AggregateTypeEnum.AVG, Pattern.compile("(?i)(平均值|平均|avg)"));
|
||||
aggregateRegexMap.put(AggregateTypeEnum.TOPN, Pattern.compile("(?i)(top)"));
|
||||
aggregateRegexMap.put(AggregateTypeEnum.DISTINCT, Pattern.compile("(?i)(uv)"));
|
||||
aggregateRegexMap.put(AggregateTypeEnum.COUNT, Pattern.compile("(?i)(总数|pv)"));
|
||||
aggregateRegexMap.put(AggregateTypeEnum.NONE, Pattern.compile("(?i)(明细)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregateTypeEnum resolve(String text) {
|
||||
|
||||
Map<AggregateTypeEnum, Integer> aggregateCount = new HashMap<>(aggregateRegexMap.size());
|
||||
for (Entry<AggregateTypeEnum, Pattern> entry : aggregateRegexMap.entrySet()) {
|
||||
Matcher matcher = entry.getValue().matcher(text);
|
||||
int count = 0;
|
||||
while (matcher.find()) {
|
||||
count++;
|
||||
}
|
||||
if (count > 0) {
|
||||
aggregateCount.put(entry.getKey(), count);
|
||||
}
|
||||
}
|
||||
return aggregateCount.entrySet().stream().max(Map.Entry.comparingByValue()).map(entry -> entry.getKey())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCompareIntentionalWords(String queryText) {
|
||||
return compareIntentionalWord.matcher(queryText).find();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.tencent.supersonic.chat.application.parser.resolver;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticQuery;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base interface for resolving query mode.
|
||||
*/
|
||||
public interface SemanticQueryResolver {
|
||||
|
||||
SemanticQuery resolve(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.tencent.supersonic.chat.application.query;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.EntityInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.api.response.QueryResultResp;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticLayer;
|
||||
import com.tencent.supersonic.chat.api.service.SemanticQuery;
|
||||
import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn;
|
||||
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
|
||||
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
|
||||
import com.tencent.supersonic.chat.application.DomainEntityService;
|
||||
import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver;
|
||||
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
|
||||
import com.tencent.supersonic.chat.domain.pojo.search.QueryState;
|
||||
import com.tencent.supersonic.chat.domain.service.ChatService;
|
||||
import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils;
|
||||
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
|
||||
import com.tencent.supersonic.common.util.context.ContextUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public abstract class BaseSemanticQuery implements SemanticQuery {
|
||||
|
||||
protected QueryModeOption queryModeOption = QueryModeOption.build();
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BaseSemanticQuery.class);
|
||||
|
||||
@Override
|
||||
public QueryResultResp execute(QueryContextReq queryCtx, ChatContext chatCtx) {
|
||||
|
||||
DomainResolver domainResolver = ContextUtils.getBean(DomainResolver.class);
|
||||
ChatService chatService = ContextUtils.getBean(ChatService.class);
|
||||
SemanticLayer semanticLayer = ContextUtils.getBean(SemanticLayer.class);
|
||||
|
||||
SemanticParseInfo semanticParse = queryCtx.getParseInfo();
|
||||
|
||||
String queryMode = semanticParse.getQueryMode();
|
||||
|
||||
if (semanticParse.getDomainId() < 0 || StringUtils.isEmpty(queryMode)) {
|
||||
// reach here some error may happen
|
||||
LOGGER.error("not find QueryMode");
|
||||
throw new RuntimeException("not find QueryMode");
|
||||
}
|
||||
|
||||
supplyMetadata(semanticLayer, queryCtx);
|
||||
|
||||
// is domain switch
|
||||
if (domainResolver.isDomainSwitch(chatCtx, queryCtx)) {
|
||||
chatService.switchContext(chatCtx);
|
||||
}
|
||||
// submit semantic query based on the result of semantic parsing
|
||||
SemanticParseInfo semanticParseInfo = getContext(chatCtx, queryCtx);
|
||||
QueryResultResp queryResponse = new QueryResultResp();
|
||||
QueryResultWithSchemaResp queryResult = semanticLayer.queryByStruct(
|
||||
SchemaInfoConverter.convertTo(semanticParseInfo), queryCtx.getUser());
|
||||
if (queryResult != null) {
|
||||
queryResponse.setQueryAuthorization(queryResult.getQueryAuthorization());
|
||||
}
|
||||
String sql = queryResult == null ? null : queryResult.getSql();
|
||||
List<Map<String, Object>> resultList = queryResult == null ? new ArrayList<>()
|
||||
: queryResult.getResultList();
|
||||
List<QueryColumn> columns = queryResult == null ? new ArrayList<>() : queryResult.getColumns();
|
||||
queryResponse.setQuerySql(sql);
|
||||
queryResponse.setQueryResults(resultList);
|
||||
queryResponse.setQueryColumns(columns);
|
||||
queryResponse.setQueryMode(queryMode);
|
||||
|
||||
// add domain info
|
||||
EntityInfo entityInfo = ContextUtils.getBean(DomainEntityService.class)
|
||||
.getEntityInfo(queryCtx, chatCtx, queryCtx.getUser());
|
||||
queryResponse.setEntityInfo(entityInfo);
|
||||
return queryResponse;
|
||||
|
||||
}
|
||||
|
||||
private void supplyMetadata(SemanticLayer semanticLayer, QueryContextReq queryCtx) {
|
||||
DefaultSemanticInternalUtils defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class);
|
||||
|
||||
SchemaMapInfo mapInfo = queryCtx.getMapInfo();
|
||||
SemanticParseInfo semanticParse = queryCtx.getParseInfo();
|
||||
Long domain = semanticParse.getDomainId();
|
||||
List<SchemaElementMatch> schemaElementMatches = mapInfo.getMatchedElements(domain.intValue());
|
||||
DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domain);
|
||||
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domain);
|
||||
|
||||
// supply metadata
|
||||
if (!CollectionUtils.isEmpty(schemaElementMatches)) {
|
||||
this.queryModeOption.addQuerySemanticParseInfo(schemaElementMatches, domainSchemaDesc,
|
||||
chaConfigRichDesc, semanticParse);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateContext(QueryResultResp queryResponse, ChatContext chatCtx, QueryContextReq queryCtx) {
|
||||
if (queryCtx.isSaveAnswer() && queryResponse != null
|
||||
&& queryResponse.getQueryState() == QueryState.NORMAL.getState()) {
|
||||
chatCtx.setParseInfo(getParseInfo(queryCtx, chatCtx));
|
||||
chatCtx.setQueryText(queryCtx.getQueryText());
|
||||
ContextUtils.getBean(ChatService.class).updateContext(chatCtx);
|
||||
}
|
||||
queryResponse.setChatContext(queryCtx.getParseInfo());
|
||||
}
|
||||
|
||||
public abstract SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx);
|
||||
|
||||
|
||||
@Override
|
||||
public SchemaElementCount match(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx) {
|
||||
return queryModeOption.match(elementMatches, queryCtx);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.tencent.supersonic.chat.application.query;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
|
||||
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EntityDetail extends BaseSemanticQuery {
|
||||
|
||||
public static String QUERY_MODE = "ENTITY_DETAIL";
|
||||
|
||||
public EntityDetail() {
|
||||
queryModeOption.setAggregation(QueryModeElementOption.unused());
|
||||
queryModeOption.setDate(QueryModeElementOption.unused());
|
||||
queryModeOption.setDimension(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST,
|
||||
1);
|
||||
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setMetric(QueryModeElementOption.unused());
|
||||
queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_MOST, 1);
|
||||
queryModeOption.setDomain(QueryModeElementOption.optional());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryMode() {
|
||||
return QUERY_MODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
|
||||
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
|
||||
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(),
|
||||
semanticParseInfo.getDimensionFilters());
|
||||
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
|
||||
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
|
||||
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.tencent.supersonic.chat.application.query;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
|
||||
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EntityListFilter extends BaseSemanticQuery {
|
||||
|
||||
public static String QUERY_MODE = "ENTITY_LIST_FILTER";
|
||||
|
||||
public EntityListFilter() {
|
||||
queryModeOption.setAggregation(QueryModeElementOption.unused());
|
||||
queryModeOption.setDate(QueryModeElementOption.unused());
|
||||
queryModeOption.setDimension(QueryModeElementOption.unused());
|
||||
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setMetric(QueryModeElementOption.unused());
|
||||
queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setDomain(QueryModeElementOption.optional());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryMode() {
|
||||
return QUERY_MODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
|
||||
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
|
||||
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(),
|
||||
semanticParseInfo.getDimensionFilters());
|
||||
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
|
||||
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
|
||||
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.tencent.supersonic.chat.application.query;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
|
||||
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EntityListTopN extends BaseSemanticQuery {
|
||||
|
||||
public static String QUERY_MODE = "ENTITY_LIST_TOPN";
|
||||
|
||||
|
||||
public EntityListTopN() {
|
||||
queryModeOption.setAggregation(QueryModeElementOption.optional());
|
||||
queryModeOption.setDate(QueryModeElementOption.optional());
|
||||
queryModeOption.setDimension(QueryModeElementOption.unused());
|
||||
queryModeOption.setFilter(QueryModeElementOption.unused());
|
||||
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setDomain(QueryModeElementOption.optional());
|
||||
queryModeOption.setSupportOrderBy(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryMode() {
|
||||
return QUERY_MODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
|
||||
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
|
||||
ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
|
||||
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
|
||||
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
|
||||
ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.tencent.supersonic.chat.application.query;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
|
||||
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EntityMetricFilter extends BaseSemanticQuery {
|
||||
|
||||
public static String QUERY_MODE = "ENTITY_METRIC_FILTER";
|
||||
|
||||
|
||||
public EntityMetricFilter() {
|
||||
queryModeOption.setAggregation(QueryModeElementOption.optional());
|
||||
queryModeOption.setDate(QueryModeElementOption.optional());
|
||||
queryModeOption.setDimension(QueryModeElementOption.unused());
|
||||
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setDomain(QueryModeElementOption.optional());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryMode() {
|
||||
return QUERY_MODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
|
||||
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
|
||||
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(),
|
||||
semanticParseInfo.getDimensionFilters());
|
||||
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
|
||||
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
|
||||
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.tencent.supersonic.chat.application.query;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.ChatContext;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.request.QueryContextReq;
|
||||
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
|
||||
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MetricCompare extends BaseSemanticQuery {
|
||||
|
||||
public static String QUERY_MODE = "METRIC_COMPARE";
|
||||
|
||||
|
||||
public MetricCompare() {
|
||||
queryModeOption.setAggregation(QueryModeElementOption.optional());
|
||||
queryModeOption.setDate(QueryModeElementOption.optional());
|
||||
queryModeOption.setDimension(QueryModeElementOption.unused());
|
||||
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
|
||||
queryModeOption.setEntity(QueryModeElementOption.unused());
|
||||
queryModeOption.setDomain(QueryModeElementOption.optional());
|
||||
queryModeOption.setSupportCompare(true);
|
||||
queryModeOption.setSupportOrderBy(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryMode() {
|
||||
return QUERY_MODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCt) {
|
||||
SemanticParseInfo semanticParseInfo = chatCt.getParseInfo();
|
||||
ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.addIfEmpty(queryCtx.getParseInfo().getDimensionFilters(),
|
||||
semanticParseInfo.getDimensionFilters());
|
||||
ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
|
||||
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
|
||||
return semanticParseInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
|
||||
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
|
||||
ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo);
|
||||
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
|
||||
ContextHelper.appendList(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
|
||||
return semanticParseInfo;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user