first commit

This commit is contained in:
jerryjzhang
2023-06-12 18:44:01 +08:00
commit dc4fc69b57
879 changed files with 573090 additions and 0 deletions

15
.gitignore vendored Normal file
View 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
View 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
View 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 © 20162023, 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
Fileshttps://github.com/minimistjs/minimist/tree/v1.2.3
License Detailshttps://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
Fileshttps://github.com/testng-team/testng/tree/6.13.1
License Detailshttps://github.com/testng-team/testng/blob/6.13.1/LICENSE.txt
8. jackson-dataformat-yaml
Fileshttps://github.com/FasterXML/jackson-dataformat-yaml/tree/jackson-dataformat-yaml-2.8.11
License Detailshttps://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
Fileshttps://github.com/GoogleChromeLabs/carlo
License Detailshttps://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
fileshttps://github.com/microsoft/TypeScript
License Detailshttps://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
Fileshttps://github.com/travist/jsencrypt
License Detailshttps://github.com/travist/jsencrypt/blob/master/LICENSE.txt

50
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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>

View File

@@ -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;
}

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<>();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<>();
}

View File

@@ -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);
}

View 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>

View File

@@ -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");
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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 {
}

View File

@@ -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();
}
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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>

View 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>

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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
View 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
View 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>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,11 @@
package com.tencent.supersonic.chat.api.pojo;
public enum SchemaElementType {
DOMAIN,
METRIC,
DIMENSION,
VALUE,
ENTITY,
ID,
DATE
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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
View 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>

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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