4 Commits

Author SHA1 Message Date
jipeli
40ea6a9396 (feature)(headless) Add tag query api (#790) 2024-03-06 11:41:47 +08:00
daikon
78d724ea83 [improvement](headless) add queryTag for tagMarket (#772) 2024-02-27 20:34:00 +08:00
lexluo09
eadbdc4e30 Merge pull request #759 from lexluo09/master
(improvement)(project) merge master to dev-0.9
2024-02-26 14:41:23 +08:00
jipeli
b8831317e9 (feature)(headless) Add tag rest api (#733) 2024-02-21 17:45:28 +08:00
1902 changed files with 86618 additions and 97195 deletions

View File

@@ -1,48 +0,0 @@
name: SuperSonic Bug report
title: "[Bug] "
description: Problems and issues with code of SuperSonic
labels: bug
body:
- type: markdown
attributes:
value: |
Thank you very much for submitting feedback to SuperSonic to help SuperSonic develop better.
If it is an idea or help wanted, please go to:
[Github Discussion](https://github.com/tencentmusic/supersonic/discussions)
- type: input
id: version
attributes:
label: SuperSonic version
description: Please tell us which version you are using.
placeholder: "0.9.8"
validations:
required: true
- type: input
id: organization
attributes:
label: Your organization
description: Please tell us your organization so that we can provide you better support and advice.
placeholder: "TME..."
validations:
required: true
- type: textarea
attributes:
label: Description
description: Describe the bug you met.
- type: checkboxes
attributes:
label: Are you willing to submit PR?
description: >
We very much look forward to developers or users to help solve the SuperSonic problem together.
If you are willing to submit a PR to fix this problem, please tick it.
options:
- label: Yes I am willing to submit a PR!
- type: markdown
attributes:
value: "Thanks for completing our form!"

View File

@@ -1,37 +0,0 @@
name: SuperSonic enhancement request
description: Add an enhancement for SuperSonic
title: "[Enhancement] "
labels: enhancement
body:
- type: markdown
attributes:
value: |
Thank you very much for your good enhancement for SuperSonic.
- type: textarea
attributes:
label: Description
description: Describe the enhancement what you want, including motivation if it exists.
- type: input
id: organization
attributes:
label: Your organization
description: Please tell us your organization so that we can provide you better support and advice.
placeholder: "TME..."
validations:
required: true
- type: checkboxes
attributes:
label: Are you willing to submit PR?
description: >
We very much look forward to developers or users to help develop the SuperSonic together.
If you are willing to submit a PR to implement this feature, please tick it.
options:
- label: Yes I am willing to submit a PR!
- type: markdown
attributes:
value: "Thanks for completing our form!"

View File

@@ -1,34 +0,0 @@
name: SuperSonic feature request
description: Suggest an idea for SuperSonic
title: "[Feature] "
labels: feature
body:
- type: markdown
attributes:
value: |
Thank you very much for your good ideas and suggestions for SuperSonic
- type: textarea
attributes:
label: Description
description: Describe your ideas and needs.
- type: input
id: organization
attributes:
label: Your organization
description: Please tell us your organization so that we can provide you better support and advice.
placeholder: "TME..."
validations:
required: true
- type: checkboxes
attributes:
label: Are you willing to submit PR?
description: >
We very much look forward to developers or users to help develop the SuperSonic together.
If you are willing to submit a PR to implement this feature, please tick it.
options:
- label: Yes I am willing to submit a PR!

View File

@@ -1,28 +0,0 @@
name: SuperSonic question request
description: Ask a question about SuperSonic
title: "[question] "
labels: question
body:
- type: markdown
attributes:
value: |
## Ask a Question about SuperSonic
Please provide a detailed description of your question or the clarification you seek regarding the SuperSonic project.
- type: textarea
id: describe-question
attributes:
label: Describe your question
description: Please provide a clear and concise description of your question.
placeholder: "Type your question here..."
validations:
required: true
- type: input
id: organization
attributes:
label: Your organization
description: Please tell us your organization so that we can provide you better support and advice.
placeholder: "TME..."
validations:
required: true

View File

@@ -1,36 +0,0 @@
# Pull Request Template
## Description
Please include a summary of the change and which issue is fixed. Also include relevant motivation and context. List any dependencies that are required for this change.
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
- [ ] Test A
- [ ] Test B
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
## Additional information
Any additional information, configuration or data that might be necessary to reproduce the issue.

View File

@@ -1,62 +0,0 @@
name: supersonic CentOS CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
container:
image: almalinux:9 # maven >=3.6.3
strategy:
matrix:
java-version: [21] # 定义要测试的JDK版本
steps:
- uses: actions/checkout@v2
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v2
with:
java-version: ${{ matrix.java-version }}
distribution: 'adopt'
- name: Reset DNF repositories
run: |
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^# baseurl=https://repo.almalinux.org|baseurl=https://mirrors.aliyun.com|g' \
/etc/yum.repos.d/almalinux*.repo
- name: Update DNF package index
run: dnf makecache
- name: Install Maven with retry
run: |
for i in {1..5}; do
dnf install -y maven && break || sleep 15
done
- name: Verify Java and Maven installation
run: |
java -version
mvn -version
- name: Cache Maven packages
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Test with Maven
run: mvn test

31
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: supersonic CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 8
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Test with Maven
run: mvn test

View File

@@ -1,32 +0,0 @@
name: Docker Publish
on:
workflow_dispatch:
inputs:
version:
description: 'Version of the Docker image'
required: true
default: 'latest'
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and publish Docker image
run: |
VERSION=${{ github.event.inputs.version }}
chmod +x docker/docker-build-publish.sh
sh docker/docker-build-publish.sh $VERSION

View File

@@ -1,45 +0,0 @@
name: supersonic mac CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: macos-latest # Specify a macOS runner
strategy:
matrix:
java-version: [21] # Define the JDK versions to test
steps:
- uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/Library/Caches/Maven # macOS Maven cache path
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Install system dependencies
run: |
brew update
brew install cmake
brew install gcc
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Test with Maven
run: mvn test

View File

@@ -1,39 +0,0 @@
name: supersonic ubuntu CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [21] # 定义要测试的JDK版本
steps:
- uses: actions/checkout@v2
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v2
with:
java-version: ${{ matrix.java-version }}
distribution: 'adopt'
- name: Cache Maven packages
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Test with Maven
run: mvn test

View File

@@ -1,39 +0,0 @@
name: supersonic windows CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: windows-latest # Specify a Windows runner
strategy:
matrix:
java-version: [21] # Add JDK 21 to the matrix
steps:
- uses: actions/checkout@v2
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v2
with:
java-version: ${{ matrix.java-version }}
distribution: 'adopt' # You might need to change this if 'adopt' doesn't support JDK 21
- name: Cache Maven packages
uses: actions/cache@v4
with:
path: ~\.m2 # Windows uses a backslash for paths
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Test with Maven
run: mvn test

4
.gitignore vendored
View File

@@ -8,7 +8,6 @@ log/
*.bin
*.log
*.tar.gz
*.zip
*.lib
assembly/runtime/*
**/dist/
@@ -19,6 +18,3 @@ assembly/runtime/*
chm_db/
__pycache__/
/dict
assembly/build/*-SNAPSHOT
**/node_modules/
benchmark/res/

View File

@@ -3,237 +3,6 @@
- All notable changes to this project will be documented in this file.
- "Breaking Changes" describes any changes that may break existing functionality or cause
compatibility issues with previous versions.
## SuperSonic [1.0.0] - 2025-08-05
### 重大特性变更 / Major Features
#### 多数据库支持扩展 / Multi-Database Support
- **Oracle数据库支持**: 新增Oracle数据库引擎类型及适配器 ([8eeed87ba](https://github.com/tencentmusic/supersonic/commit/8eeed87ba) by supersonicbi)
- **StarRocks支持**: 支持StarRocks和多catalog功能 ([33268bf3d](https://github.com/tencentmusic/supersonic/commit/33268bf3d) by zyclove)
- **SAP HANA支持**: 新增SAP HANA数据库适配支持 ([2e28a4c7a](https://github.com/tencentmusic/supersonic/commit/2e28a4c7a) by wwsheng009)
- **DuckDB支持**: 支持DuckDB数据库 ([a058dc8b6](https://github.com/tencentmusic/supersonic/commit/a058dc8b6) by jerryjzhang)
- **Kyuubi支持**: 支持Kyuubi Presto Trino ([5e3bafb95](https://github.com/tencentmusic/supersonic/commit/5e3bafb95) by zyclove)
- **OpenSearch支持**: 新增OpenSearch支持 ([d942d35c9](https://github.com/tencentmusic/supersonic/commit/d942d35c9) by zyclove)
#### 智能问答增强 / AI-Enhanced Query Processing
- **LLM纠错器**: 新增LLM物理SQL纠错器 ([f899d23b6](https://github.com/tencentmusic/supersonic/commit/f899d23b6) by 柯慕灵)
- **记忆管理**: Agent记忆管理启用few-shot优先机制 ([fae9118c2](https://github.com/tencentmusic/supersonic/commit/fae9118c2) by feelshana)
- **结构化查询**: 支持struct查询中的offset子句 ([d2a43a99c](https://github.com/tencentmusic/supersonic/commit/d2a43a99c) by jerryjzhang)
- **向量召回优化**: 优化嵌入向量召回机制 ([8c6ae6252](https://github.com/tencentmusic/supersonic/commit/8c6ae6252) by lexluo09)
#### 权限管理系统 / Permission Management
- **Agent权限**: 支持agent级别的权限管理 ([b5aa6e046](https://github.com/tencentmusic/supersonic/commit/b5aa6e046) by jerryjzhang)
- **用户管理**: 支持用户删除功能 ([1c9cf788c](https://github.com/tencentmusic/supersonic/commit/1c9cf788c) by supersonicbi)
- **鉴权优化**: 全面优化鉴权与召回机制 ([1faf84e37](https://github.com/tencentmusic/supersonic/commit/1faf84e37), [7e6639df8](https://github.com/tencentmusic/supersonic/commit/7e6639df8) by guilinlewis)
### 架构升级 / Architecture Upgrades
#### 核心框架升级 / Core Framework Upgrades
- **SpringBoot 3升级**: 完成SpringBoot 3.x升级 ([07f6be51c](https://github.com/tencentmusic/supersonic/commit/07f6be51c) by mislayming)
- **依赖升级**: 升级依赖包并修复安全漏洞 ([232a20227](https://github.com/tencentmusic/supersonic/commit/232a20227) by beat4ocean)
- **LangChain4j更新**: 替换已废弃的LangChain4j APIs ([acffc03c7](https://github.com/tencentmusic/supersonic/commit/acffc03c7) by beat4ocean)
- **Swagger升级**: 使用SpringDoc支持Swagger在Spring 3.x ([758d170bb](https://github.com/tencentmusic/supersonic/commit/758d170bb) by jerryjzhang)
#### 许可证变更 / License Changes
- **Apache 2.0**: 从MIT更改为Apache 2.0许可证 ([0aa002882](https://github.com/tencentmusic/supersonic/commit/0aa002882) by jerryjzhang)
### 性能优化 / Performance Improvements
#### 系统性能 / System Performance
- **GC优化**: 实现Generational ZGC ([3fc1ec42b](https://github.com/tencentmusic/supersonic/commit/3fc1ec42b) by beat4ocean)
- **Docker优化**: 减少Docker镜像体积 ([614917ba7](https://github.com/tencentmusic/supersonic/commit/614917ba7) by kino)
- **并行处理**: 嵌入向量并行执行优化 ([8c6ae6252](https://github.com/tencentmusic/supersonic/commit/8c6ae6252) by lexluo09)
- **记忆评估**: 记忆评估性能优化 ([524ec38ed](https://github.com/tencentmusic/supersonic/commit/524ec38ed) by yudong)
- **多平台构建**: 支持Docker多平台构建 ([da6d28c18](https://github.com/tencentmusic/supersonic/commit/da6d28c18) by jerryjzhang)
#### 数据处理优化 / Data Processing Optimization
- **日期格式**: 支持更多日期字符串格式 ([2b13866c0](https://github.com/tencentmusic/supersonic/commit/2b13866c0) by supersonicbi)
- **SQL优化**: 优化SQL生成和执行性能 ([0ab764329](https://github.com/tencentmusic/supersonic/commit/0ab764329) by jerryjzhang)
- **模型关联**: 优化模型关联查询性能 ([47c2595fb](https://github.com/tencentmusic/supersonic/commit/47c2595fb) by Willy-J)
### 功能增强 / Feature Enhancements
#### 前端界面优化 / Frontend Improvements
- **图表导出**: 消息支持导出图表图片 ([ce9ae1c0c](https://github.com/tencentmusic/supersonic/commit/ce9ae1c0c) by pisces)
- **路由重构**: 重构语义建模路由交互 ([82c63a7f2](https://github.com/tencentmusic/supersonic/commit/82c63a7f2) by tristanliu)
- **权限界面**: 统一助理权限设置交互界面 ([46d64d78f](https://github.com/tencentmusic/supersonic/commit/46d64d78f) by tristanliu)
- **图表优化**: 优化ChatMsg图表条件 ([06fb6ba74](https://github.com/tencentmusic/supersonic/commit/06fb6ba74) by FredTsang)
- **数据格式**: 提取formatByDataFormatType()方法 ([9ffdba956](https://github.com/tencentmusic/supersonic/commit/9ffdba956) by FredTsang)
#### 开发体验 / Developer Experience
- **构建脚本**: 优化Web应用构建脚本 ([baae7f74b](https://github.com/tencentmusic/supersonic/commit/baae7f74b) by zyclove)
- **GitHub Actions**: 优化GitHub Actions镜像推送 ([6a4458a57](https://github.com/tencentmusic/supersonic/commit/6a4458a57) by lexluo09)
- **基准测试**: 改进基准测试,增加解析结果分析 ([97710a90c](https://github.com/tencentmusic/supersonic/commit/97710a90c) by Antgeek)
### Bug修复 / Bug Fixes
#### 核心功能修复 / Core Function Fixes
- **插件功能**: 修复插件功能无法调用/结果被NL2SQL覆盖问题 ([c75233e37](https://github.com/tencentmusic/supersonic/commit/c75233e37) by QJ_wonder)
- **维度别名**: 修复映射阶段维度值别名不生效问题 ([785bda6cd](https://github.com/tencentmusic/supersonic/commit/785bda6cd) by feelshana)
- **模型字段**: 修复模型字段更新问题 ([6bd897084](https://github.com/tencentmusic/supersonic/commit/6bd897084) by WDEP)
- **多轮对话**: 修复headless中字段查询及多轮对话使用问题 ([be0447ae1](https://github.com/tencentmusic/supersonic/commit/be0447ae1) by QJ_wonder)
#### NPE异常修复 / NPE Exception Fixes
- **聊天查询**: 修复EmbeddingMatchStrategy.detectByBatch() NPE异常 ([6d907b6ad](https://github.com/tencentmusic/supersonic/commit/6d907b6ad) by wangyong)
- **文件处理**: 修复FileHandlerImpl.convert2Resp() 维度值数据行首字符为空格异常 ([da172a030](https://github.com/tencentmusic/supersonic/commit/da172a030) by wangyong)
- **头部服务**: 修复多处headless NPE问题 ([79a44b27e](https://github.com/tencentmusic/supersonic/commit/79a44b27e) by jerryjzhang)
- **解析信息**: 修复getParseInfo中的NPE ([dce9a8a58](https://github.com/tencentmusic/supersonic/commit/dce9a8a58) by supersonicbi)
#### SQL兼容性修复 / SQL Compatibility Fixes
- **SQL处理**: 修复SQL前后换行符导致的语句结尾";"删除问题 ([55ac3d1aa](https://github.com/tencentmusic/supersonic/commit/55ac3d1aa) by wangyong)
- **查询别名**: DictUtils.constructQuerySqlReq针对sql query增加别名 ([042791762](https://github.com/tencentmusic/supersonic/commit/042791762) by andybj0228)
- **SQL变量**: 支持SQL脚本变量替换 ([0709575cd](https://github.com/tencentmusic/supersonic/commit/0709575cd) by wanglongqiang)
#### 前端Bug修复 / Frontend Bug Fixes
- **UI样式**: 修复问答对话右侧历史对话模块样式异常 ([c33a85b58](https://github.com/tencentmusic/supersonic/commit/c33a85b58) by wangyong)
- **推荐维度**: 修复页面不显示推荐下钻维度问题 ([62b9db679](https://github.com/tencentmusic/supersonic/commit/62b9db679) by WDEP)
- **图表显示**: 修复饼图显示条件问题 ([1b8cd7f0d](https://github.com/tencentmusic/supersonic/commit/1b8cd7f0d) by WDEP)
- **负数支持**: 支持负数显示 ([2552e2ae4](https://github.com/tencentmusic/supersonic/commit/2552e2ae4) by FredTsang)
- **百分比显示**: 支持bar图needMultiply100显示正确百分比值 ([8abfc923a](https://github.com/tencentmusic/supersonic/commit/8abfc923a) by coosir)
- **TypeScript错误**: 修复前端TypeScript错误 ([5585b9e22](https://github.com/tencentmusic/supersonic/commit/5585b9e22) by poncheen)
#### 系统兼容性修复 / System Compatibility Fixes
- **Windows脚本**: 修复Windows daemon.bat路径配置问题 ([e5a41765b](https://github.com/tencentmusic/supersonic/commit/e5a41765b) by 柯慕灵)
- **字符编码**: 将utf8编码修改为utf8mb4,解决字符问题 ([2e81b190a](https://github.com/tencentmusic/supersonic/commit/2e81b190a) by Kun Gu)
- **记忆缓存**: 修复记忆管理中因缓存无法存储的问题 ([81cd60d2d](https://github.com/tencentmusic/supersonic/commit/81cd60d2d) by guilinlewis)
- **Mac兼容**: 降级djl库以支持Mac Intel机器 ([bf3213e8f](https://github.com/tencentmusic/supersonic/commit/bf3213e8f) by jerryjzhang)
### 数据管理优化 / Data Management Improvements
#### 维度指标管理 / Dimension & Metric Management
- **维度检索**: 修复维度和指标检索及百分比显示问题 ([d8fe2ed2b](https://github.com/tencentmusic/supersonic/commit/d8fe2ed2b) by 木鱼和尚)
- **查询导出**: 基于queryColumns导出数据 ([11d1264d3](https://github.com/tencentmusic/supersonic/commit/11d1264d3) by FredTsang)
- **表格排序**: 移除表格defaultSortOrder ([32675387d](https://github.com/tencentmusic/supersonic/commit/32675387d) by FredTsang)
- **维度搜索**: 修复维度搜索带key查询范围超出问题 ([269f146c1](https://github.com/tencentmusic/supersonic/commit/269f146c1) by wangyong)
### 测试和质量保证 / Testing & Quality Assurance
#### 单元测试 / Unit Testing
- **测试修复**: 修复单元测试用例 ([91e4b51ef](https://github.com/tencentmusic/supersonic/commit/91e4b51ef) by jerryjzhang)
- **模型测试**: 修复ModelCreateForm.tsx错误 ([d2aa73b85](https://github.com/tencentmusic/supersonic/commit/d2aa73b85) by Antgeek)
### 重要变更说明 / Breaking Changes
#### 升级注意事项 / Upgrade Notes
1. **SpringBoot 3升级**: 可能需要更新依赖配置和代码适配
2. **许可证变更**: 从MIT变更为Apache 2.0,请注意法律合规
3. **API接口调整**: 部分API接口为支持新功能进行了调整
4. **数据库兼容**: 新增多种数据库支持,配置方式有所变化
### 完整提交统计 / Commit Statistics
- **总提交数**: 419个提交
- **主要贡献者**:
- jerryjzhang: 158次提交
- supersonicbi: 22次提交
- zyclove: 20次提交
- beat4ocean: 15次提交
- guilinlewis: 11次提交
- wangyong: 11次提交
- 其他贡献者: 182次提交
- **涉及模块**: headless, chat, auth, common, webapp, launcher, docker
- **时间跨度**: 2024年11月1日 - 2025年8月5日
### 致谢 / Acknowledgments
感谢所有为SuperSonic 1.0.0版本贡献代码、文档、测试和建议的开发者们!🎉
#### 核心贡献者 / Core Contributors
- **jerryjzhang** - 项目维护者,核心架构设计与实现
- **supersonicbi** - 核心功能开发,多数据库支持
- **beat4ocean** - 架构升级,依赖管理,安全优化
- **zyclove** - 数据库适配,构建优化
- **guilinlewis** - 鉴权系统,召回优化
- **wangyong** - Bug修复NPE异常处理
#### 活跃贡献者 / Active Contributors
- **WDEP** - 前端优化,图表功能
- **FredTsang** - Chat SDK优化数据导出
- **feelshana** - 记忆管理,向量召回
- **QJ_wonder** - 插件功能,多轮对话
- **Willy-J** - 模型关联,数据库兼容
- **iridescentpeo** - 查询优化,模型管理
- **tristanliu** - 前端路由,权限界面
- **mislayming** - SpringBoot 3升级
- **Antgeek** - 基准测试,模型修复
- **柯慕灵** - LLM纠错器Windows脚本
- **superhero** - 项目管理,代码审查
#### 其他重要贡献者 / Other Important Contributors
- **木鱼和尚** - 维度指标检索优化
- **pisces** - 图表导出功能
- **lexluo09** - 并行处理GitHub Actions
- **andybj0228** - SQL查询优化
- **wanglongqiang** - SQL变量支持
- **Hyman_bz** - StarRocks支持
- **wwsheng009** - SAP HANA适配
- **poncheen** - TypeScript错误修复
- **kino** - Docker镜像优化
- **coosir** - 前端百分比显示
- **Kun Gu** - 字符编码优化
- **chixiaopao** - NPE异常修复
- **naimehao** - 核心功能修复
- **yudong** - 记忆评估优化
- **mroldx** - 数据库脚本更新
- **ChPi** - 解析器性能优化
- **Hwting** - Docker配置优化
#### 特别感谢 / Special Thanks
感谢所有提交Issue、参与讨论、提供反馈的社区用户你们的每一个建议都让SuperSonic变得更好
#### 社区支持 / Community Support
SuperSonic是一个开源项目我们欢迎更多开发者加入
- 🔗 **GitHub**: https://github.com/tencentmusic/supersonic
- 📖 **文档**: 详见项目README和Wiki
- 🐛 **Issue报告**: 欢迎提交Bug和功能请求
- 🚀 **贡献代码**: 欢迎提交Pull Request
- 💬 **社区讨论**: 加入我们的技术交流群
#### 未来展望 / Future Vision
SuperSonic 1.0.0是一个重要的里程碑,但这只是开始。我们将继续:
- 🌟 **持续优化性能和稳定性**
- 🔧 **扩展更多数据库和AI模型支持**
- 🎨 **改善用户体验和界面设计**
- 📚 **完善文档和最佳实践**
- 🤝 **建设更活跃的开源社区**
**让我们一起把SuperSonic做得更好**
---
*如果您在使用过程中遇到问题或有改进建议欢迎随时与我们交流。每一份贡献都让SuperSonic更加强大*
## SuperSonic [0.9.8] - 2024-11-01
- Add LLM management module to reuse connection across agents.
- Add ChatAPP configuration sub-module in Agent Management.
- Enhance dimension value management sub-module.
- Enhance memory management and term management sub-module.
- Enhance semantic translation of complex S2SQL.
- Enhance user experience in Chat UI.
- Introduce LLM-based semantic corrector and data interpreter.
## SuperSonic [0.9.2] - 2024-06-01
### Added
- support multiple rounds of dialogue
- add term configuration and identification to help LLM learn private domain knowledge
- support configuring LLM parameters in the agent
- metric market supports searching in natural language
### Updated
- introducing WorkFlow, Mapper, Parser, and Corrector support jump execution
- Introducing the concept of Model-Set to simplify Domain management
- overall optimization and upgrade of system pages
- optimize startup script
## SuperSonic [0.9.0] - 2024-04-03
### Added
- add tag abstraction and enhance tag marketplace management.
- headless-server provides Chat API interface.
### Updated
- migrate chat-core core component to headless-core.
## SuperSonic [0.8.6] - 2024-02-23

467
LICENSE
View File

@@ -1,41 +1,468 @@
Apache License Version 2.0
MIT License
Copyright (2025) The SuperSonic Project Authors. All rights reserved.
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:
SuperSonic is licensed under the Apache License 2.0, with the following additional conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1. The commercial usage of SuperSonic:
a. SuperSonic may be utilized commercially, including as a frontend and backend service without modifying the source
code and logo.
b. a commercial license must be obtained from the author if you want to develop and distribute a derivative work based
on SuperSonic.
Please contact supersonicbi@qq.com by email to inquire about licensing matters.
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.
2. As a contributor, you should agree that:
a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
Other dependencies and licenses:
b. Your contributed code may be used for commercial purposes, including but not limited to its cloud edition.
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0.
Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
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
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

View File

@@ -1,72 +1,53 @@
[中文](README_CN.md) | [日本語版](README_JP.md) | [Docs](https://supersonicbi.github.io/)
[中文介绍](README_CN.md) | [文档中心](https://github.com/tencentmusic/supersonic/wiki)
![Java CI](https://github.com/tencentmusic/supersonic/workflows/supersonic%20CI/badge.svg)
# SuperSonic
# SuperSonic (超音数)
SuperSonic is the next-generation AI+BI platform that unifies **Chat BI** (powered by LLM) and **Headless BI** (powered by semantic layer) paradigms. This unification ensures that Chat BI has access to the same curated and governed semantic data models as traditional BI. Furthermore, the implementation of both paradigms benefit from each other:
**SuperSonic is the next-generation LLM-powered data analytics platform that integrates ChatBI and HeadlessBI**. 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 logical semantic models (definition of entities/metrics/dimensions/tags, along with their meaning, context and relationships) on top of physical data models, and **no data modification or copying** is required. Meanwhile, SuperSonic is designed to be **highly extensible**, allowing custom functionalities to be added and configured with Java SPI.
- Chat BI's Text2SQL gets augmented with context-retrieval from semantic models.
- Headless BI's query interface gets extended with natural language API.
<img src="https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_ideas.png" height="75%" width="75%" />
SuperSonic provides a **Chat BI 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 logical semantic models (definition of metric/dimension/tag, along with their meaning and relationships) through a **Headless BI interface**. Meanwhile, SuperSonic is designed to be extensible and composable, allowing custom implementations to be added and configured with Java SPI.
<img src="https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_demo.gif" height="100%" width="100%" />
<img src="./docs/images/supersonic_demo.gif" height="100%" width="100%" align="center"/>
## Motivation
The emergence of Large Language Model (LLM) like ChatGPT is reshaping the way information is retrieved, leading to a new paradigm in the field of data analytics known as Chat BI. To implement Chat BI, both academia and industry are primarily focused on harnessing the power of LLMs to convert natural language into SQL, commonly referred to as Text2SQL or NL2SQL. While some approaches show promising results, their **reliability** falls short for large-scale real-world applications.
The emergence of Large Language Model (LLM) like ChatGPT is reshaping the way information is retrieved. In the field of data analytics, both academia and industry are primarily focused on leveraging LLM to convert natural language into SQL (so called Text2SQL or NL2SQL). While some approaches exhibit promising results, their **reliability** and **efficiency** are insufficient for real-world applications.
Meanwhile, another emerging paradigm called Headless BI, which focuses on constructing unified semantic data models, has garnered significant attention. Headless BI is implemented through a universal semantic layer that exposes consistent data semantics via an open API.
From our perspective, the key to filling the real-world gap lies in three aspects:
1. Integrate ChatBI with HeadlessBI encapsulating underlying data context (joins, keys, formulas, etc) to **reduce complexity**.
2. Augment the LLM with schema mappers(as a kind of preprocessor) and semantic correctors(as a kind of postprocessor) to **mitigate hallucination**.
3. Utilize rule-based schema parsers when necessary to **improve efficiency**(in terms of latency and cost).
From our perspective, the integration of Chat BI and Headless BI has the potential to enhance the Text2SQL generation in two dimensions:
1. Incorporate data semantics (such as business terms, column values, etc.) into the prompt, enabling LLM to better understand the semantics and **reduce hallucination**.
2. Offload the generation of advanced SQL syntax (such as join, formula, etc.) from LLM to the semantic layer to **reduce complexity**.
With these ideas in mind, we develop SuperSonic as a practical reference implementation and use it to power our real-world products. Additionally, to facilitate further development we decide to open source SuperSonic as an extensible framework.
With these ideas in mind, we develop SuperSonic as a practical reference implementation and use it to power our real-world products. Additionally, to facilitate further development of ChatBI, we decide to open source SuperSonic as an extensible framework.
## Out-of-the-box Features
- Built-in Chat BI interface for *business users* to enter natural language queries
- Built-in Headless BI interface for *analytics engineers* to build semantic data models
- Built-in rule-based semantic parser to improve efficiency in certain scenarios (e.g. demonstration, integration testing)
- Built-in support for input auto-completion, multi-turn conversation as well as post-query recommendation
- Built-in support for three-level data access control: dataset-level, column-level and row-level
- Built-in ChatBI interface for *business users* to enter natural language queries
- Built-in HeadlessBI interface for *analytics engineers* to build semantic models
- Built-in GUI for *system administrators* to manage chat agents and third-party plugins
- Support input auto-completion as well as query recommendation
- Support multi-turn conversation and history context management
- Support four-level permission control: domain-level, model-level, column-level and row-level
## Extensible Components
The high-level architecture and main process flow is as follows:
<img src="https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_components.png" height="65%" width="65%" />
<img src="./docs/images/supersonic_components.png" height="65%" width="65%" align="center"/>
- **Knowledge Base:** extracts schema information periodically from the semantic models and build dictionary and index to facilitate schema mapping.
- **Schema Mapper:** identifies references to schema elements(metrics/dimensions/entities/values) in user queries. It matches the query text against the knowledge base.
- **Semantic Parser:** understands user queries and generates semantic query statement. It consists of a combination of rule-based and LLM-based parsers, each of which deals with specific scenarios.
- **Semantic Parser:** understands user queries and extracts semantic information. It consists of a combination of rule-based and model-based parsers, each of which deals with specific scenarios.
- **Semantic Corrector:** checks validity of semantic query statement and performs correction if necessary. It consists of a combination of rule-based and LLM-based correctors, each of which deals with specific scenarios.
- **Semantic Corrector:** checks validity of extracted semantic information and performs correction and optimization if needed.
- **Semantic Translator:** converts semantic query statement into SQL statement that can be executed against physical data models.
- **Semantic Interpreter:** performs execution according to extracted semantic information. It generates SQL statements and executes them against physical data models.
- **Chat Plugin:** extends functionality with third-party tools. Given a list of configured plugins with descriptions and sample questions, an LLM will be leveraged to select the most suitable one.
- **Chat Memory:** encapsulates a collection of historical query trajectories that can be recalled to facilitate few-shot prompting.
- **Chat Plugin:** extends functionality with third-party tools. The LLM is going to select the most suitable one, given all configured plugins with function description and sample questions.
## Quick Demo
### Online playground
Visit http://117.72.46.148:9080 to register and experience as a new user. Please do not modify system configurations. We will restart to reset configurations regularly every weekend.
### Docker Deployment
- Install Docker and docker-compose.
- Download the docker-compose.yml file; Execute: wget https://raw.githubusercontent.com/tencentmusic/supersonic/master/docker/docker-compose.yml.
- Execute "docker-compose up -d".
- Open a browser and visit http://localhost:9080 to start exploring.
### Local build
SuperSonic comes with sample semantic models as well as chat conversations that can be used as a starting point. Please follow the steps:
- Download the latest prebuilt binary from the [release page](https://github.com/tencentmusic/supersonic/releases)
@@ -75,10 +56,10 @@ SuperSonic comes with sample semantic models as well as chat conversations that
## Build and Development
Please refer to project [Docs](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E9%83%A8%E7%BD%B2/).
Please refer to project [wiki](https://github.com/tencentmusic/supersonic/wiki).
## WeChat Contact
Please follow SuperSonic wechat official account:
<img src="https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_wechat_oa.png" height="50%" width="50%" />
<img src="./docs/images/supersonic_wechat_oa.png" height="50%" width="50%" align="center"/>

View File

@@ -1,72 +1,49 @@
[English](README.md) | [日本語版](README_JP.md) | [文档中心](https://supersonicbi.github.io/)
# SuperSonic (超音数)
# SuperSonic
**SuperSonic融合ChatBI和HeadlessBI打造新一代的数据分析平台**。通过SuperSonic的问答对话界面用户能够使用自然语言查询数据系统会选择合适的可视化图表呈现结果。SuperSonic不需要修改或复制数据只需要在物理数据模型之上构建逻辑语义模型指标/维度/实体的定义以及他们的业务含义、相互间关系等即可开启数据问答体验。与此同时SuperSonic被设计为可插拔的框架采用Java SPI机制来扩展定制功能。
**SuperSonic融合Chat BIpowered by LLM和Headless BIpowered by 语义层打造新一代的BI平台**。这种融合确保了Chat BI能够与传统BI一样访问统一化治理的语义数据模型。此外两种BI新范式都从中获得收益
- Chat BI的Text2SQL生成通过检索语义数据模型得到增强。
- Headless BI的查询接口通过支持自然语言API得到拓展。
<img src="https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_ideas.png" height="75%" width="75%" />
通过SuperSonic的问答对话界面用户能够使用自然语言查询数据系统会选择合适的可视化图表呈现结果。SuperSonic不需要修改或复制数据只需要在物理数据模型之上构建逻辑语义模型定义指标/维度/实体/标签以及它们的业务含义、相互关系等即可开启数据问答体验。与此同时SuperSonic被设计为可插拔的框架采用Java SPI机制来扩展定制功能。
<img src="https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_demo.gif" height="100%" width="100%" />
<img src="./docs/images/supersonic_demo.gif" height="100%" width="100%" align="center"/>
## 项目动机
大型语言模型LLM如ChatGPT的出现正在重塑信息检索的方式,引领数据分析领域的一种新范式被称为Chat BI。为了实现Chat BI,学术界和工业界主要关注利用LLM的能力将自然语言转换为SQL通常称为Text2SQL或NL2SQL。尽管一些方法显示出有希望的结果,但它们在大规模实际应用中的可靠性还不足
大型语言模型LLMs如ChatGPT的出现正在重塑信息检索的方式。在数据分析领域,学术界和工业界主要关注利用深度学习模型将自然语言查询转换为SQL查询。虽然一些工作显示出有前景的结果,但它们的可靠性还达不到生产可用的要求
与此同时另一种新兴范式被称为Headless BI它专注于构建统一的语义数据模型并引起了广泛的关注。Headless BI通过一个通用的语义层来实现通过开放的API公开一致的数据语义。
从我们的角度来看Chat BI和Headless BI的融合有潜力在两个方面增强Text2SQL的能力
1. 将数据语义如业务术语、列值等纳入提示词中使LLM能够更好地理解语义以**减少幻觉**。
2. 将高级SQL语法如连接、公式等的生成从LLM卸载到语义层以**减少复杂度**。
在我们看来,为了在实际场景发挥价值,有三个关键点:
1. 融合HeadlessBI通过统一语义层封装底层数据细节关联、键值、公式等降低SQL生成的**复杂度**。
2. 通过一前一后的模式映射器和语义修正器来缓解LLM常见的**幻觉**现象。
3. 设计启发式的规则,在一些特定场景提升语义解析的**效率**。
为了验证上述想法我们开发了SuperSonic项目并将其应用在实际的内部产品中。与此同时我们将SuperSonic作为一个可扩展的框架开源希望能够促进数据问答对话领域的进一步发展。
## 开箱即用的特性
- 内置Chat BI界面以便*业务用户*输入数据查询。
- 内置Headless BI界面以便*分析工程师*构建语义模型。
- 内置基于规则的语义解析器在特定场景比如DEMO演示、集成测试可以提升推理效率
- 支持文本输入联想、多轮对话、查询问题推荐等高级特征
- 支持三级权限控制:数据集级、列级、行级
- 内置ChatBI界面以便*业务用户*输入数据查询。
- 内置HeadlessBI界面以便*分析工程师*构建语义模型。
- 内置图形用户界面以便*系统管理员*管理第三方插件和对话助理
- 支持文本输入联想查询问题推荐。
- 支持多轮对话,根据语境自动切换上下文
- 支持四级权限控制:主题域级、模型级、列级、行级。
## 易于扩展的组件
SuperSonic的整体架构和主流程如下图所示
<img src="https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_components.png" height="65%" width="65%" />
<img src="./docs/images/supersonic_components.png" height="65%" width="65%" align="center"/>
- **模型知识库(Knowledge Base)** 定期从语义模型中提取相关的模式信息,构建词典和索引,以便后续的模式映射。
- **模式映射器(Schema Mapper)** 将自然语言文本在知识库中进行匹配,为后续的语义解析提供相关信息。
- **语义解析器(Semantic Parser)** 理解用户查询并抽取语义信息,生成语义查询语句S2SQL
- **语义解析器(Semantic Parser)** 理解用户查询并抽取语义信息,其由一组基于规则和基于模型的解析器组成,每个解析器可应对不同的特定场景
- **语义修正器(Semantic Corrector)** 检查语义查询语句的合法性,对不合法的信息做修正和优化处理。
- **语义修正器(Semantic Corrector)** 检查语义信息的合法性,对不合法的信息做修正和优化处理。
- **语义翻译器(Semantic Translator)** 将语义查询语句翻译成可在物理数据模型上执行的SQL语句
- **语义解释器(Semantic Interpreter)** 根据语义信息生成物理SQL执行查询
- **问答插件(Chat Plugin)** 通过第三方工具扩展功能。给定所有配置的插件及其功能描述和示例问题,大语言模型将选择最合适的插件。
- **问答记忆(Chat Memory)** 将历史的查询轨迹进行封装可被召回作为few-shot样例嵌入提示词。
## 快速体验
### 线上环境体验
访问http://117.72.46.148:9080 注册新用户体验. 请勿修改系统配置。我们每周末定期重启重置配置。
### Docker部署
- 安装好Docker以及docker-compose
- 下载docker-compose.yml执行命令wget https://raw.githubusercontent.com/tencentmusic/supersonic/master/docker/docker-compose.yml
- 执行:"docker-compose up -d"
- 在浏览器访问http://localhost:9080 开启探索
### 本地构建
SuperSonic自带样例的语义模型和问答对话只需以下三步即可快速体验
- 从[release page](https://github.com/tencentmusic/supersonic/releases)下载预先构建好的发行包
@@ -75,10 +52,10 @@ SuperSonic自带样例的语义模型和问答对话只需以下三步即可
## 如何构建和部署
请参考项目[文档](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E9%83%A8%E7%BD%B2/)。
请参考项目[wiki](https://github.com/tencentmusic/supersonic/wiki)。
## 微信联系方式
欢迎关注微信公众号:
<img src="https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_wechat_oa.png" height="50%" width="50%" />
<img src="./docs/images/supersonic_wechat_oa.png" height="50%" width="50%" align="center"/>

View File

@@ -1,80 +0,0 @@
[英文版](README.md) | [中国語版](README_CN.md)
# SuperSonic
**SuperSonicは、LLM大規模言語モデルによるChat BIと、セマンティックレイヤーによるHeadless BIを統合した次世代のBIプラットフォームです。** この統合により、Chat BIは従来のBIと同様に、統一されたガバナンスされたセマンティックデータモデルにアクセスできます。さらに、両方のBIパラダイムは統合から恩恵を受けます
- Chat BIのText2SQLは、セマンティックモデルからのコンテキスト検索によって強化されます。
- Headless BIのクエリインターフェースは、自然言語APIによって拡張されます。
![SuperSonicのアイデア](https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_ideas.png)
SuperSonicは、ユーザーが自然言語でデータをクエリし、適切なチャートで結果を視覚化できる**Chat BIインターフェース**を提供します。このような体験を実現するために必要なのは、**Headless BIインターフェース**を通じて論理的なセマンティックモデル(メトリック/ディメンション/タグの定義、それらの意味と関係などを構築することだけです。同時に、SuperSonicは拡張可能で構成可能な設計を採用しており、Java SPIを使用してカスタム実装を追加および設定できます。
![SuperSonicのデモ](https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_demo.gif)
## プロジェクトの動機
ChatGPTのような大規模言語モデルLLMの出現は、情報検索の方法を再定義し、データ分析分野における新しいパラダイムであるChat BIをリードしています。Chat BIを実装するために、学術界と産業界は主に、自然言語をSQLに変換するLLMの能力を活用することに焦点を当てています。これは一般にText2SQLまたはNL2SQLと呼ばれます。一部のアプローチは有望な結果を示していますが、大規模な実世界のアプリケーションでの**信頼性**はまだ不十分です。
一方で、統一されたセマンティックデータモデルを構築することに焦点を当てた別の新興パラダイムであるHeadless BIも、大きな注目を集めています。Headless BIは、オープンAPIを介して一貫したデータセマンティクスを公開するユニバーサルセマンティックレイヤーを通じて実装されます。
私たちの観点から見ると、Chat BIとHeadless BIの統合は、Text2SQL生成を2つの側面で強化する可能性があります
1. データセマンティクスビジネス用語、列の値などをプロンプトに組み込むことで、LLMがセマンティクスをよりよく理解し、**幻覚を減らす**ことができます。
2. 高度なSQL構文結合、式などの生成をLLMからセマンティックレイヤーにオフロードすることで、**複雑さを減らす**ことができます。
これらのアイデアを念頭に置いて、私たちはSuperSonicプロジェクトを開発し、実際の製品でそれを使用しています。同時に、SuperSonicを拡張可能なフレームワークとしてオープンソース化し、データクエリ対話分野のさらなる発展を促進したいと考えています。
## 初期設定で利用可能な機能
- *ビジネスユーザー*が自然言語クエリを入力できる組み込みのChat BIインターフェース。
- *分析エンジニア*がセマンティックモデルを構築できる組み込みのHeadless BIインターフェース。
- 特定のシナリオ(例:デモンストレーション、統合テスト)で推論効率を向上させるための組み込みのルールベースのセマンティックパーサー。
- 入力自動補完、マルチターン会話、クエリ後の質問推奨などの高度な機能をサポート。
- データセットレベル、列レベル、行レベルの3レベルのデータアクセス制御をサポート。
## 拡張可能なコンポーネント
高レベルのアーキテクチャとメインのプロセスフローは以下の通りです:
![SuperSonicのコンポーネント](https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_components.png)
- **モデル知識ベース(Knowledge Base)** セマンティックモデルから定期的にスキーマ情報を抽出し、辞書とインデックスを構築して、スキーママッピングを容易にします。
- **スキーママッパー(Schema Mapper)** ユーザークエリ内のスキーマ要素(メトリック/ディメンション/エンティティ/値)を識別します。クエリテキストを知識ベースと照合します。
- **セマンティックパーサー(Semantic Parser)** ユーザークエリを理解し、セマンティッククエリステートメントS2SQLを生成します。
- **セマンティック修正器(Semantic Corrector)** セマンティッククエリステートメントの妥当性をチェックし、必要に応じて修正と最適化を行います。
- **セマンティックトランスレーター(Semantic Translator)** セマンティッククエリステートメントを、物理データモデル上で実行可能なSQLステートメントに変換します。
- **チャットプラグイン(Chat Plugin)** サードパーティツールで機能を拡張します。すべての設定されたプラグインとその機能説明、サンプル質問が与えられた場合、LLMは最も適切なプラグインを選択します。
## クイックデモ
### オンラインプレイグラウンド
http://117.72.46.148:9080 にアクセスして、新規ユーザーとして登録して体験してください。システム設定を変更しないでください。毎週末に定期的に再起動して設定をリセットします。
### Dockerのデプロイメント
- Dockerおよびdocker-composeをインストールします。
- docker-compose.ymlファイルをダウンロードします。コマンドを実行しますwget https://raw.githubusercontent.com/tencentmusic/supersonic/master/docker/docker-compose.yml。
- docker-compose up -dを実行します。
- ブラウザを開いてhttp://localhost:9080にアクセスし、探索を開始します。
### ローカルビルド
SuperSonicには、サンプルのセマンティックモデルとチャット会話が付属しており、以下の手順で簡単に体験できます
- [リリースページ](https://github.com/tencentmusic/supersonic/releases)から最新のプリビルドバイナリをダウンロード
- スクリプト "assembly/bin/supersonic-daemon.sh start" を実行して、スタンドアロンJavaサービスを起動
- ブラウザで http://localhost:9080 にアクセスして探索を開始
## ビルドと開発
プロジェクト[ドキュメント](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E9%83%A8%E7%BD%B2/)を参照してください。
## WeChat連絡先
SuperSonicの公式WeChatアカウントをフォローしてください
![SuperSonicのWeChat公式アカウント](https://github.com/supersonicbi/supersonic-website/blob/main/static/img/supersonic_wechat_oa.png)

View File

@@ -1,143 +1,72 @@
@echo off
setlocal enabledelayedexpansion
setlocal
chcp 65001
set "sbinDir=%~dp0"
call %sbinDir%/supersonic-common.bat %*
set "baseDir=%~dp0.."
set "buildDir=%baseDir%\build"
set "runtimeDir=%baseDir%\..\runtime"
set "pip_path=pip3"
set "service=%~1"
cd %projectDir%
if "%service%"=="" (
set service=%standalone_service%
rem 1. build backend java modules
del /q "%buildDir%\*.tar.gz" 2>NUL
call mvn -f "%baseDir%\..\pom.xml" clean package -DskipTests
IF ERRORLEVEL 1 (
ECHO Failed to build backend Java modules.
EXIT /B 1
)
call mvn help:evaluate -Dexpression=project.version > temp.txt
for /f "delims=" %%i in (temp.txt) do (
set line=%%i
if not "!line:~0,1!"=="[" (
set MVN_VERSION=!line!
)
)
del temp.txt
cd %baseDir%
rem 2. move package to build
echo f|xcopy "%baseDir%\..\launchers\standalone\target\*.tar.gz" "%buildDir%\supersonic-standalone.tar.gz"
rem 3. build frontend webapp
cd "%baseDir%\..\webapp"
call start-fe-prod.bat
copy /y "%baseDir%\..\webapp\supersonic-webapp.tar.gz" "%buildDir%\"
if "%service%"=="webapp" (
call :buildWebapp
tar xvf supersonic-webapp.tar.gz
move /y supersonic-webapp webapp
move /y webapp %projectDir%\launchers\%STANDALONE_SERVICE%\target\classes
goto :EOF
) else (
call :buildJavaService
call :buildWebapp
call :packageRelease
goto :EOF
IF ERRORLEVEL 1 (
ECHO Failed to build frontend webapp.
EXIT /B 1
)
rem 4. copy webapp to java classpath
cd "%buildDir%"
tar -zxvf supersonic-webapp.tar.gz
move supersonic-webapp webapp
move webapp ..\..\launchers\standalone\target\classes
:buildJavaService
set "model_name=%service%"
echo "starting building supersonic-%model_name% service"
call mvn -f %projectDir% clean package -DskipTests -Dspotless.skip=true
IF ERRORLEVEL 1 (
ECHO Failed to build backend Java modules.
ECHO Please check Maven and Java versions are compatible.
ECHO Current Java: %JAVA_HOME%
ECHO Current Maven: %MAVEN_HOME%
EXIT /B 1
)
rem 5. build backend python modules
if "%service%"=="pyllm" (
echo "start installing python modules with pip: ${pip_path}"
set requirementPath="%baseDir%/../chat/python/requirements.txt"
%pip_path% install -r %requirementPath%
echo "install python modules success"
)
REM extract and copy files to deployment directory
cd %projectDir%\launchers\%model_name%\target
if exist "launchers-%model_name%-%MVN_VERSION%-bin.tar.gz" (
echo "Extracting launchers-%model_name%-%MVN_VERSION%-bin.tar.gz..."
tar -xf "launchers-%model_name%-%MVN_VERSION%-bin.tar.gz"
if exist "launchers-%model_name%-%MVN_VERSION%" (
echo "Copying files to deployment directory..."
xcopy /E /Y "launchers-%model_name%-%MVN_VERSION%\*" "%buildDir%\supersonic-%model_name%-%MVN_VERSION%\"
)
)
call :BUILD_RUNTIME
copy /y %projectDir%\launchers\%model_name%\target\*.tar.gz %buildDir%\
echo "finished building supersonic-%model_name% service"
cd %baseDir%
goto :EOF
:BUILD_RUNTIME
rem 6. reset runtime
IF EXIST "%runtimeDir%" (
echo begin to delete dir : %runtimeDir%
rd /s /q "%runtimeDir%"
) ELSE (
echo %runtimeDir% does not exist, create directly
)
mkdir "%runtimeDir%"
tar -zxvf "%buildDir%\supersonic-standalone.tar.gz" -C "%runtimeDir%"
for /d %%f in ("%runtimeDir%\launchers-standalone-*") do (
move "%%f" "%runtimeDir%\supersonic-standalone"
)
:buildWebapp
echo "starting building supersonic webapp"
cd %projectDir%\webapp
call start-fe-prod.bat
copy /y supersonic-webapp.tar.gz %buildDir%\
rem check build result
IF ERRORLEVEL 1 (
ECHO Failed to build frontend webapp.
EXIT /B 1
)
echo "finished building supersonic webapp"
goto :EOF
:packageRelease
set "model_name=%service%"
set "release_dir=supersonic-%model_name%-%MVN_VERSION%"
set "service_name=launchers-%model_name%-%MVN_VERSION%"
echo "starting packaging supersonic release"
cd %buildDir%
if exist %release_dir% rmdir /s /q %release_dir%
if exist %release_dir%.zip del %release_dir%.zip
rem check if release directory already exists from buildJavaService
if exist %release_dir% (
echo "Release directory already prepared by buildJavaService"
) else (
mkdir %release_dir%
rem package java service
tar xvf %service_name%-bin.tar.gz 2>nul
if errorlevel 1 (
echo "Warning: tar command failed, trying PowerShell extraction..."
powershell -Command "Expand-Archive -Path '%service_name%-bin.tar.gz' -DestinationPath '.' -Force"
)
for /d %%D in ("%service_name%\*") do (
move "%%D" "%release_dir%"
)
rmdir /s /q %service_name% 2>nul
)
rem package webapp
if exist supersonic-webapp.tar.gz (
tar xvf supersonic-webapp.tar.gz 2>nul
if errorlevel 1 (
echo "Warning: tar command failed, trying PowerShell extraction..."
powershell -Command "Expand-Archive -Path 'supersonic-webapp.tar.gz' -DestinationPath '.' -Force"
)
move /y supersonic-webapp webapp
echo {"env": ""} > webapp\supersonic.config.json
move /y webapp %release_dir%
del supersonic-webapp.tar.gz 2>nul
)
rem verify deployment structure
if exist "%release_dir%\lib\launchers-%model_name%-%MVN_VERSION%.jar" (
echo "Deployment structure verified successfully"
) else (
echo "Warning: Main jar file not found in deployment structure"
echo "Expected: %release_dir%\lib\launchers-%model_name%-%MVN_VERSION%.jar"
)
rem generate zip file
powershell -Command "Compress-Archive -Path '%release_dir%' -DestinationPath '%release_dir%.zip' -Force"
if errorlevel 1 (
echo "Warning: PowerShell compression failed, release directory still available: %release_dir%"
) else (
echo "Successfully created release package: %release_dir%.zip"
)
del %service_name%-bin.tar.gz 2>nul
echo "finished packaging supersonic release"
goto :EOF
rem 7. copy webapp to runtime
tar -zxvf "%buildDir%\supersonic-webapp.tar.gz" -C "%buildDir%"
if not exist "%runtimeDir%\supersonic-standalone\webapp" mkdir "%runtimeDir%\supersonic-standalone\webapp"
xcopy /s /e /h /y "%buildDir%\supersonic-webapp\*" "%runtimeDir%\supersonic-standalone\webapp"
if not exist "%runtimeDir%\supersonic-standalone\conf\webapp" mkdir "%runtimeDir%\supersonic-standalone\conf\webapp"
xcopy /s /e /h /y "%runtimeDir%\supersonic-standalone\webapp\*" "%runtimeDir%\supersonic-standalone\conf\webapp"
rd /s /q "%buildDir%\supersonic-webapp"
endlocal

View File

@@ -1,94 +1,58 @@
#!/usr/bin/env bash
set -x
sbinDir=$(cd "$(dirname "$0")"; pwd)
chmod +x $sbinDir/supersonic-common.sh
source $sbinDir/supersonic-common.sh
cd $projectDir
MVN_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout | grep -v '^\[' | sed -n '/^[0-9]/p')
if [ -z "$MVN_VERSION" ]; then
echo "Failed to retrieve Maven project version."
exit 1
fi
echo "Maven project version: $MVN_VERSION"
cd $baseDir
service=$1
if [ -z "$service" ]; then
service=${STANDALONE_SERVICE}
#1. build backend java modules
rm -fr ${buildDir}/*.tar.gz
rm -fr dist
set +x
mvn -f $baseDir/../ clean package -DskipTests
# check build result
if [ $? -ne 0 ]; then
echo "Failed to build backend Java modules."
exit 1
fi
function buildJavaService {
model_name=$1
echo "starting building supersonic-${model_name} service"
mvn -f $projectDir clean package -DskipTests -Dspotless.skip=true
if [ $? -ne 0 ]; then
echo "Failed to build backend Java modules."
exit 1
fi
cp $projectDir/launchers/${model_name}/target/*.tar.gz ${buildDir}/
echo "finished building supersonic-${model_name} service"
}
#2. move package to build
cp $baseDir/../launchers/headless/target/*.tar.gz ${buildDir}/supersonic-headless.tar.gz
cp $baseDir/../launchers/chat/target/*.tar.gz ${buildDir}/supersonic-chat.tar.gz
cp $baseDir/../launchers/standalone/target/*.tar.gz ${buildDir}/supersonic-standalone.tar.gz
function buildWebapp {
echo "starting building supersonic webapp"
chmod +x $projectDir/webapp/start-fe-prod.sh
cd $projectDir/webapp
sh ./start-fe-prod.sh
# check build result
if [ $? -ne 0 ]; then
echo "Failed to build frontend webapp."
exit 1
fi
cp -fr ./supersonic-webapp.tar.gz ${buildDir}/
# check build result
if [ $? -ne 0 ]; then
echo "Failed to get supersonic webapp package."
exit 1
fi
echo "finished building supersonic webapp"
}
#3. build frontend webapp
chmod +x $baseDir/../webapp/start-fe-prod.sh
cd ../webapp
sh ./start-fe-prod.sh
cp -fr ./supersonic-webapp.tar.gz ${buildDir}/
function packageRelease {
model_name=$1
release_dir=supersonic-${model_name}-${MVN_VERSION}
service_name=launchers-${model_name}-${MVN_VERSION}
echo "starting packaging supersonic release"
cd $buildDir
[ -d "$release_dir" ] && rm -rf "$release_dir"
[ -f "$release_dir.zip" ] && rm -f "$release_dir.zip"
mkdir $release_dir
# package webapp
tar xvf supersonic-webapp.tar.gz
mv supersonic-webapp webapp
# check webapp build result
if [ $? -ne 0 ]; then
echo "Failed to get supersonic webapp package."
exit 1
fi
json='{"env": "''"}'
echo $json > webapp/supersonic.config.json
mv webapp $release_dir/
# package java service
tar xvf $service_name-bin.tar.gz
mv $service_name/* $release_dir/
# generate zip file
zip -r $release_dir.zip $release_dir
# delete intermediate files
rm supersonic-webapp.tar.gz $service_name-bin.tar.gz
rm -rf webapp $service_name $release_dir
echo "finished packaging supersonic release"
}
#1. build backend services
if [ "$service" == "webapp" ]; then
buildWebapp
target_path=$projectDir/launchers/$STANDALONE_SERVICE/target/classes
tar xvf $projectDir/webapp/supersonic-webapp.tar.gz -C $target_path
rm -rf $target_path/webapp
mv $target_path/supersonic-webapp $target_path/webapp
else
buildJavaService $service
buildWebapp
packageRelease $service
# check build result
if [ $? -ne 0 ]; then
echo "Failed to build frontend webapp."
exit 1
fi
#4. copy webapp to java classpath
cd $buildDir
tar xvf supersonic-webapp.tar.gz
mv supersonic-webapp webapp
cp -fr webapp ../../launchers/headless/target/classes
cp -fr webapp ../../launchers/chat/target/classes
cp -fr webapp ../../launchers/standalone/target/classes
rm -fr ${buildDir}/webapp
#5. build backend python modules
if [ "$service" == "pyllm" ]; then
echo "start installing python modules with pip: ${pip_path}"
requirementPath=$baseDir/../chat/python/requirements.txt
${pip_path} install -r ${requirementPath}
echo "install python modules success"
fi
#6. reset runtime
rm -fr $runtimeDir/supersonic*
moveAllToRuntime
setEnvToWeb chat
setEnvToWeb headless

View File

@@ -1,6 +0,0 @@
set "sbinDir=%~dp0"
set "baseDir=%~dp0.."
set "buildDir=%baseDir%\build"
set "main_class=com.tencent.supersonic.StandaloneLauncher"
set "standalone_service=standalone"
set "projectDir=%baseDir%\.."

View File

@@ -1,16 +1,110 @@
#!/usr/bin/env bash
# environment parameters
python_path=${PYTHON_PATH:-"python3"}
pip_path=${PIP_PATH:-"pip3"}
sbinDir=$(cd "$(dirname "$0")"; pwd)
baseDir=$(cd "$sbinDir/.." && pwd -P)
runtimeDir=$baseDir/runtime
runtimeDir=$baseDir/../runtime
buildDir=$baseDir/build
projectDir=$baseDir/..
readonly CHAT_APP_NAME="supersonic_chat"
readonly HEADLESS_APP_NAME="supersonic_headless"
readonly PYLLM_APP_NAME="supersonic_pyllm"
readonly STANDALONE_APP_NAME="supersonic_standalone"
readonly CHAT_SERVICE="chat"
readonly HEADLESS_SERVICE="headless"
readonly PYLLM_SERVICE="pyllm"
readonly STANDALONE_SERVICE="standalone"
readonly PYLLM_HOST="127.0.0.1"
readonly PYLLM_PORT="9092"
function setEnvToWeb {
model_name=$1
json='{"env": "'$model_name'"}'
echo $json > ${runtimeDir}/supersonic-${model_name}/webapp/supersonic.config.json
echo $json > $baseDir/../launchers/${model_name}/target/classes/webapp/supersonic.config.json
}
function moveToRuntime {
model_name=$1
file="${buildDir}/supersonic-${model_name}.tar.gz"
if [ -f "$file" ]; then
tar -zxvf "$file" -C ${runtimeDir}
mv ${runtimeDir}/launchers-${model_name}-* ${runtimeDir}/supersonic-${model_name}
mkdir -p ${runtimeDir}/supersonic-${model_name}/webapp
cp -fr ${buildDir}/webapp/* ${runtimeDir}/supersonic-${model_name}/webapp
else
echo "File $file does not exist. Skipping the move to runtime."
fi
}
function moveAllToRuntime {
mkdir -p ${runtimeDir}
tar xvf ${buildDir}/supersonic-webapp.tar.gz -C ${buildDir}
mv ${buildDir}/supersonic-webapp ${buildDir}/webapp
moveToRuntime chat
moveToRuntime headless
moveToRuntime standalone
rm -fr ${buildDir}/webapp
}
# run java service
function runJavaService {
javaRunDir=${runtimeDir}/supersonic-${model_name}
local_app_name=$1
libDir=$javaRunDir/lib
confDir=$javaRunDir/conf
CLASSPATH=""
CLASSPATH=$CLASSPATH:$confDir
for jarPath in $libDir/*.jar; do
CLASSPATH=$CLASSPATH:$jarPath
done
export CLASSPATH
export LANG="zh_CN.UTF-8"
cd $javaRunDir
if [[ "$JAVA_HOME" == "" ]]; then
JAVA_HOME=$(ls /usr/jdk64/jdk* -d 2>/dev/null | xargs | awk '{print "'$local_app_name'"}')
fi
export PATH=$JAVA_HOME/bin:$PATH
command="-Dfile.encoding="UTF-8" -Duser.language="Zh" -Duser.region="CN" -Duser.timezone="GMT+08" -Dapp_name=${local_app_name} -Xms1024m -Xmx2048m "$main_class
mkdir -p $javaRunDir/logs
if [[ "$is_test" == "true" ]]; then
java -Dspring.profiles.active="dev" $command >/dev/null 2>$javaRunDir/logs/error.log &
else
java $command $javaRunDir >/dev/null 2>$javaRunDir/logs/error.log &
fi
}
# run python service
function runPythonService {
pythonRunDir=${runtimeDir}/supersonic-${model_name}/pyllm
cd $pythonRunDir
nohup ${python_path} supersonic_pyllm.py > $pythonRunDir/pyllm.log 2>&1 &
# add health check
for i in {1..10}
do
echo "pyllm health check attempt $i..."
response=$(curl -s http://${PYLLM_HOST}:${PYLLM_PORT}/health)
echo "pyllm health check response: $response"
status_ok="Healthy"
if [[ $response == *$status_ok* ]] ; then
echo "pyllm Health check passed."
break
else
if [ "$i" -eq 10 ]; then
echo "pyllm Health check failed after 10 attempts."
echo "May still downloading model files. Please check pyllm.log in runtime directory."
fi
echo "Retrying after 5 seconds..."
sleep 5
fi
done
}

View File

@@ -1,113 +1,118 @@
@echo off
setlocal
chcp 65001
set "sbinDir=%~dp0"
call %sbinDir%/supersonic-common.bat %*
call %sbinDir%/supersonic-env.bat %*
set "baseDir=%~dp0.."
set "runtimeDir=%baseDir%\..\runtime"
set "buildDir=%baseDir%\build"
set "main_class=com.tencent.supersonic.StandaloneLauncher"
set "python_path=python"
set "pip_path=pip3"
set "standalone_service=standalone"
set "pyllm_service=pyllm"
set "javaRunDir=%runtimeDir%\supersonic-standalone"
set "pythonRunDir=%runtimeDir%\supersonic-standalone\pyllm"
set "command=%~1"
set "service=%~2"
set "profile=%~3"
if "%service%"=="" (
set "service=%standalone_service%"
)
if "%profile%"=="" (
set "profile=%S2_DB_TYPE%"
IF "%service%"=="pyllm" (
SET "llmProxy=PythonLLMProxy"
)
set "model_name=%service%"
REM fix path configuration - point to the correct release package directory
set "releaseDir=%buildDir%\supersonic-%service%-1.0.0-SNAPSHOT"
cd %releaseDir%
call :BUILD_RUNTIME
if "%command%"=="restart" (
call :stop
call :start
call :STOP
call :START
goto :EOF
) else if "%command%"=="start" (
call :start
call :START
goto :EOF
) else if "%command%"=="stop" (
call :stop
goto :EOF
call :STOP
goto :EOF
) else if "%command%"=="reload" (
call :reloadExamples
goto :EOF
call :RELOAD_EXAMPLE
goto :EOF
) else (
echo "Use command {start|stop|restart} to run."
goto :EOF
)
:start
call :runJavaService
:START
if "%service%"=="%pyllm_service%" (
call :START_PYTHON
call :START_JAVA
goto :EOF
)
call :START_JAVA
goto :EOF
:STOP
call :STOP_PYTHON
call :STOP_JAVA
goto :EOF
:START_PYTHON
echo 'python service starting, see logs in pyllm/pyllm.log'
cd "%pythonRunDir%"
start /B %python_path% supersonic_pyllm.py > %pythonRunDir%\pyllm.log 2>&1
timeout /t 10 >nul
echo 'python service started'
goto :EOF
:stop
call :stopJavaService
goto :EOF
:runJavaService
echo 'java service starting, see logs in logs/'
echo 'Using release directory: %releaseDir%'
REM use release package directory as base path
set "libDir=%releaseDir%\lib"
set "confDir=%releaseDir%\conf"
set "webDir=%releaseDir%\webapp"
set "logDir=%releaseDir%\logs"
REM fix variable name matching problem
set "CLASSPATH=%releaseDir%;%webDir%;%libDir%\*;%confDir%"
set "MAIN_CLASS=%main_class%"
REM add port configuration
set "property=-Dfile.encoding=UTF-8 -Duser.language=Zh -Duser.region=CN -Duser.timezone=GMT+08 -Dspring.profiles.active=%profile% -Dserver.port=9080"
set "java_command=%property% -Xms1024m -Xmx2048m -cp "%CLASSPATH%" %MAIN_CLASS%"
if not exist %logDir% mkdir %logDir%
REM check if the main jar file exists
if not exist "%libDir%\launchers-standalone-1.0.0-SNAPSHOT.jar" (
echo "Error: Main jar file not found in %libDir%"
echo "Please make sure the application has been built and packaged correctly."
goto :EOF
)
echo 'Main Class: %MAIN_CLASS%'
echo 'Profile: %profile%'
echo 'Starting Java service...'
REM start service and save logs
start /B java %java_command% > "%logDir%\supersonic.log" 2>&1
timeout /t 15 >nul
REM check service status
netstat -an | findstr ":9080" >nul
if errorlevel 1 (
echo "Warning: Port 9080 is not listening"
echo "Please check the log file: %logDir%\supersonic.log"
if exist "%logDir%\supersonic.log" (
echo "Recent log entries:"
powershell -Command "Get-Content '%logDir%\supersonic.log' | Select-Object -Last 10"
)
) else (
echo "Service started successfully on port 9080"
echo "You can access the application at: http://localhost:9080"
)
:START_JAVA
echo 'java service starting, see logs in logs/'
cd "%javaRunDir%"
if not exist "%runtimeDir%\supersonic-standalone\logs" mkdir "%runtimeDir%\supersonic-standalone\logs"
set "libDir=%runtimeDir%\supersonic-standalone\lib"
set "confDir=%runtimeDir%\supersonic-standalone\conf"
set "webDir=%runtimeDir%\supersonic-standalone\webapp"
set "classpath=%confDir%;%webDir%;%libDir%\*"
set "java-command=-Dfile.encoding=UTF-8 -Duser.language=Zh -Duser.region=CN -Duser.timezone=GMT+08 -Xms1024m -Xmx2048m -cp %CLASSPATH% %MAIN_CLASS%"
start /B java %java-command% >nul 2>&1
timeout /t 10 >nul
echo 'java service started'
goto :EOF
:stopJavaService
echo 'Stopping Java service...'
:STOP_PYTHON
for /f "tokens=2" %%i in ('tasklist ^| findstr /i "python"') do (
taskkill /PID %%i /F
echo "python service (PID = %%i) is killed."
)
goto :EOF
:STOP_JAVA
for /f "tokens=2" %%i in ('tasklist ^| findstr /i "java"') do (
taskkill /PID %%i /F
echo "java service (PID = %%i) is killed."
)
goto :EOF
endlocal
:RELOAD_EXAMPLE
cd "%runtimeDir%\supersonic-standalone\pyllm\sql"
start %python_path% examples_reload_run.py
goto :EOF
:BUILD_RUNTIME
rem 6. reset runtime
if exist "%runtimeDir%" goto :EOF
mkdir "%runtimeDir%"
tar -zxvf "%buildDir%\supersonic-standalone.tar.gz" -C "%runtimeDir%"
for /d %%f in ("%runtimeDir%\launchers-standalone-*") do (
move "%%f" "%runtimeDir%\supersonic-standalone"
)
rem 7. copy webapp to runtime
tar -zxvf "%buildDir%\supersonic-webapp.tar.gz" -C "%buildDir%"
if not exist "%runtimeDir%\supersonic-standalone\webapp" mkdir "%runtimeDir%\supersonic-standalone\webapp"
xcopy /s /e /h /y "%buildDir%\supersonic-webapp\*" "%runtimeDir%\supersonic-standalone\webapp"
if not exist "%runtimeDir%\supersonic-standalone\conf\webapp" mkdir "%runtimeDir%\supersonic-standalone\conf\webapp"
xcopy /s /e /h /y "%runtimeDir%\supersonic-standalone\webapp\*" "%runtimeDir%\supersonic-standalone\conf\webapp"
rd /s /q "%buildDir%\supersonic-webapp"

View File

@@ -1,113 +1,143 @@
#!/usr/bin/env bash
set -x
sbinDir=$(cd "$(dirname "$0")"; pwd)
chmod +x $sbinDir/supersonic-common.sh
source $sbinDir/supersonic-common.sh
source $sbinDir/supersonic-env.sh
# 1.init environment parameters
if [ ! -d "$runtimeDir" ]; then
echo "the runtime dir does not exist move all to runtime"
moveAllToRuntime
fi
set +x
command=$1
service=$2
profile=$3
if [ -z "$service" ]; then
service=${STANDALONE_SERVICE}
fi
if [ -z "$profile" ]; then
profile=${S2_DB_TYPE}
app_name=$STANDALONE_APP_NAME
main_class="com.tencent.supersonic.StandaloneLauncher"
model_name=$service
if [ "$service" == "pyllm" ]; then
model_name=${STANDALONE_SERVICE}
export llmProxy=PythonLLMProxy
fi
model_name=$service
cd $baseDir
# 2.set main class
function setMainClass {
if [ "$service" == $CHAT_SERVICE ]; then
main_class="com.tencent.supersonic.ChatLauncher"
elif [ "$service" == $HEADLESS_SERVICE ]; then
main_class="com.tencent.supersonic.HeadlessLauncher"
else
main_class="com.tencent.supersonic.StandaloneLauncher"
fi
}
setMainClass
# 3.set app name
function setAppName {
if [ "$service" == $CHAT_SERVICE ]; then
app_name=$CHAT_APP_NAME
elif [ "$service" == $HEADLESS_SERVICE ]; then
app_name=$HEADLESS_APP_NAME
else
app_name=$STANDALONE_APP_NAME
elif [ "$service" == $PYLLM_SERVICE ]; then
app_name=$PYLLM_APP_NAME
fi
}
setAppName
function runJavaService {
javaRunDir=$baseDir
local_app_name=$1
libDir=$baseDir/lib
confDir=$baseDir/conf
CLASSPATH=""
CLASSPATH=$CLASSPATH:$confDir
for jarPath in $libDir/*.jar; do
CLASSPATH=$CLASSPATH:$jarPath
done
export CLASSPATH
export LANG="zh_CN.UTF-8"
cd $javaRunDir
if [[ "$JAVA_HOME" == "" ]]; then
JAVA_HOME=$(ls /usr/jdk64/jdk* -d 2>/dev/null | xargs | awk '{print "'$local_app_name'"}')
fi
export PATH=$JAVA_HOME/bin:$PATH
command="-Dfile.encoding=UTF-8 -Duser.language=Zh -Duser.region=CN -Duser.timezone=GMT+08
-Dapp_name=${local_app_name} -Xms1024m -Xmx2048m -XX:+UseZGC -XX:+ZGenerational $main_class"
mkdir -p $javaRunDir/logs
java -Dspring.profiles.active="$profile" $command >/dev/null 2>$javaRunDir/logs/error.log &
function reloadExamples {
pythonRunDir=${runtimeDir}/supersonic-${model_name}/pyllm
cd $pythonRunDir/sql
${python_path} examples_reload_run.py
}
function start() {
function start()
{
local_app_name=$1
echo "Starting ${local_app_name}"
pid=$(ps aux | grep ${local_app_name} | grep -v grep | awk '{print $2}')
pid=$(ps aux |grep ${local_app_name} | grep -v grep | awk '{print $2}')
if [[ "$pid" == "" ]]; then
runJavaService ${local_app_name}
if [[ ${local_app_name} == $PYLLM_APP_NAME ]]; then
runPythonService ${local_app_name}
else
runJavaService ${local_app_name}
fi
else
echo "Process (PID = $pid) is running."
return 1
fi
echo "Start success"
}
function stop() {
echo "Stopping $1"
function stop()
{
pid=$(ps aux | grep $1 | grep -v grep | awk '{print $2}')
if [[ "$pid" == "" ]]; then
echo "Process $1 is not running!"
echo "Process $1 is not running !"
return 1
else
kill -9 $pid
echo "Process (PID = $pid) is killed!"
echo "Process (PID = $pid) is killed !"
return 0
fi
echo "Stop success"
}
setMainClass
setAppName
function reload()
{
if [[ $1 == $PYLLM_APP_NAME ]]; then
reloadExamples
fi
}
# 4. execute command operation
case "$command" in
start)
start ${app_name}
;;
if [ "$service" == $PYLLM_SERVICE ]; then
echo "Starting $app_name"
start $app_name
echo "Starting $STANDALONE_APP_NAME"
start $STANDALONE_APP_NAME
else
echo "Starting $app_name"
start $app_name
fi
echo "Start success"
;;
stop)
stop $app_name
;;
echo "Stopping $app_name"
stop $app_name
echo "Stopping $PYLLM_APP_NAME"
stop $PYLLM_APP_NAME
echo "Stop success"
;;
reload)
echo "Reloading ${app_name}"
reload ${app_name}
echo "Reload success"
;;
restart)
stop ${app_name}
start ${app_name}
;;
if [ "$service" == $PYLLM_SERVICE ]; then
echo "Stopping ${app_name}"
stop ${app_name}
echo "Stopping ${STANDALONE_APP_NAME}"
stop $STANDALONE_APP_NAME
echo "Starting ${app_name}"
start ${app_name}
echo "Starting ${STANDALONE_APP_NAME}"
start $STANDALONE_APP_NAME
else
echo "Stopping ${app_name}"
stop ${app_name}
echo "Starting ${app_name}"
start ${app_name}
fi
echo "Restart success"
;;
*)
echo "Use command {start|stop|restart} to run."
exit 1
echo "Use command {start|stop|restart} to run."
exit 1
esac

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env sh
export SUPERSONIC_VERSION=latest
docker-compose -f docker-compose.yml -p supersonic up

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env sh
export SUPERSONIC_VERSION=latest
#### Set below DB configs to connect to your own database
# Supported DB_TYPE: h2, mysql, postgres
export S2_DB_TYPE=h2
export S2_DB_HOST=
export S2_DB_PORT=
export S2_DB_USER=
export S2_DB_PASSWORD=
export S2_DB_DATABASE=
docker run --rm -it -d \
--name supersonic_standalone \
-p 9080:9080 \
-e S2_DB_TYPE=${S2_DB_TYPE} \
-e S2_DB_HOST=${S2_DB_HOST} \
-e S2_DB_PORT=${S2_DB_PORT} \
-e S2_DB_USER=${S2_DB_USER} \
-e S2_DB_PASSWORD=${S2_DB_PASSWORD} \
-e S2_DB_DATABASE=${S2_DB_DATABASE} \
supersonicbi/supersonic:${SUPERSONIC_VERSION}

View File

@@ -1,8 +0,0 @@
:: Set below DB configs to connect to your own database
:: Supported DB_TYPE: h2, mysql, postgres
set "S2_DB_TYPE=h2"
set "S2_DB_HOST="
set "S2_DB_PORT="
set "S2_DB_USER="
set "S2_DB_PASSWORD="
set "S2_DB_DATABASE="

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
#### Set below DB configs to connect to your own database
# Comment out below exports to config your DB connection
# Supported DB_TYPE: h2, mysql, postgres
#export S2_DB_TYPE=h2
#export S2_DB_HOST=
#export S2_DB_PORT=
#export S2_DB_USER=
#export S2_DB_PASSWORD=
#export S2_DB_DATABASE=

View File

@@ -21,12 +21,8 @@
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/../../assembly/bin</directory>
<excludes>
<exclude>supersonic-build.sh</exclude>
<exclude>supersonic-build.bat</exclude>
</excludes>
<outputDirectory>bin</outputDirectory>
<directory>${project.basedir}/../../chat/python</directory>
<outputDirectory>pyllm</outputDirectory>
<fileMode>0777</fileMode>
<directoryMode>0755</directoryMode>
</fileSet>

View File

@@ -34,8 +34,8 @@
</dependencies>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@@ -1,16 +1,15 @@
package com.tencent.supersonic.auth.api.authentication.adaptor;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
import com.tencent.supersonic.common.pojo.User;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;
/** UserAdaptor defines some interfaces for obtaining user and organization information */
/**
* UserAdaptor defines some interfaces for obtaining user and organization information
*/
public interface UserAdaptor {
List<String> getUserNames();
@@ -21,25 +20,9 @@ public interface UserAdaptor {
void register(UserReq userReq);
void deleteUser(long userId);
String login(UserReq userReq, HttpServletRequest request);
String login(UserReq userReq, String appKey);
String login(UserReq userReq);
List<User> getUserByOrg(String key);
Set<String> getUserAllOrgId(String userName);
String getPassword(String userName);
void resetPassword(String userName, String password, String newPassword);
UserToken generateToken(String name, String userName, long expireTime);
void deleteUserToken(Long id);
UserToken getUserToken(Long id);
List<UserToken> getUserTokens(String userName);
}

View File

@@ -1,56 +1,36 @@
package com.tencent.supersonic.auth.api.authentication.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
@Data
@Configuration
public class AuthenticationConfig {
@Value("${s2.authentication.exclude.path:XXX}")
@Value("${authentication.exclude.path:XXX}")
private String excludePath;
@Value("${s2.authentication.include.path:/api}")
@Value("${authentication.include.path:/api}")
private String includePath;
@Value("${s2.authentication.strategy:http}")
private String strategy;
@Value("${s2.authentication.enable:false}")
@Value("${authentication.enable:false}")
private boolean enabled;
@Value("${s2.authentication.token.default.appKey:supersonic}")
private String tokenDefaultAppKey;
@Value("${authentication.token.secret:secret}")
private String tokenSecret;
@Value("${s2.authentication.token.appSecret:supersonic:WIaO9YRRVt+7QtpPvyWsARFngnEcbaKBk"
+ "783uGFwMrbJBaochsqCH62L4Kijcb0sZCYoSsiKGV/zPml5MnZ3uQ==}")
private String tokenAppSecret;
@Value("${s2.authentication.token.http.header.key:Authorization}")
@Value("${authentication.token.http.header.key:Authorization}")
private String tokenHttpHeaderKey;
@Value("${s2.authentication.token.http.app.key:App-Key}")
private String tokenHttpHeaderAppKey;
@Value("${s2.authentication.app.appId:appId}")
@Value("${authentication.app.appId:appId}")
private String appId;
@Value("${s2.authentication.app.timestamp:timestamp}")
@Value("${authentication.app.timestamp:timestamp}")
private String timestamp;
@Value("${s2.authentication.app.signature:signature}")
@Value("${authentication.app.signature:signature}")
private String signature;
@Value("${s2.authentication.token.timeout:72000000}")
private Long tokenTimeout;
public Map<String, String> getAppKeyToSecretMap() {
return Arrays.stream(this.tokenAppSecret.split(",")).map(s -> s.split(":"))
.collect(Collectors.toMap(e -> e[0].trim(), e -> e[1].trim()));
}
}

View File

@@ -13,8 +13,16 @@ public class UserConstants {
public static final String TOKEN_USER_EMAIL = "token_user_email";
public static final String TOKEN_IS_ADMIN = "token_is_admin";
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

@@ -23,4 +23,5 @@ public class Organization {
private List<Organization> subOrganizations = Lists.newArrayList();
private boolean isRoot;
}

View File

@@ -1,17 +1,14 @@
package com.tencent.supersonic.common.pojo;
package com.tencent.supersonic.auth.api.authentication.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.sql.Timestamp;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
public class User {
private Long id;
@@ -23,28 +20,21 @@ public class User implements Serializable {
private Integer isAdmin;
private Timestamp lastLogin;
public static User get(Long id, String name, String displayName, String email,
Integer isAdmin) {
return new User(id, name, displayName, email, isAdmin, null);
public static User get(Long id, String name, String displayName, String email, Integer isAdmin) {
return new User(id, name, displayName, email, isAdmin);
}
public static User get(Long id, String name) {
return new User(id, name, name, name, 0, null);
return new User(id, name, name, name, 0);
}
public static User getDefaultUser() {
return new User(1L, "admin", "admin", "admin@email", 1, null);
}
public static User getVisitUser() {
return new User(1L, "visit", "visit", "visit@email", 0, null);
public static User getFakeUser() {
return new User(1L, "admin", "admin", "admin@email", 1);
}
public static User getAppUser(int appId) {
String name = String.format("app_%s", appId);
return new User(1L, name, name, "", 1, null);
return new User(1L, name, name, "", 1);
}
public String getDisplayName() {
@@ -54,4 +44,5 @@ public class User implements Serializable {
public boolean isSuperAdmin() {
return isAdmin != null && isAdmin == 1;
}
}

View File

@@ -1,20 +0,0 @@
package com.tencent.supersonic.auth.api.authentication.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserToken {
private Integer id;
private String name;
private String userName;
private String token;
private Long expireTime;
private Date createDate;
private Date expireDate;
}

View File

@@ -1,20 +1,7 @@
package com.tencent.supersonic.auth.api.authentication.pojo;
import com.tencent.supersonic.common.pojo.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_CREATE_TIME;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_IS_ADMIN;
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;
@Data
@AllArgsConstructor
@@ -22,27 +9,14 @@ public class UserWithPassword extends User {
private String password;
public UserWithPassword(Long id, String name, String displayName, String email, String password,
Integer isAdmin) {
super(id, name, displayName, email, isAdmin, null);
public UserWithPassword(Long id, String name, String displayName, String email, String password, Integer isAdmin) {
super(id, name, displayName, email, isAdmin);
this.password = password;
}
public static UserWithPassword get(Long id, String name, String displayName, String email,
String password, Integer isAdmin) {
public static UserWithPassword get(Long id, String name, String displayName,
String email, String password, Integer isAdmin) {
return new UserWithPassword(id, name, displayName, email, password, isAdmin);
}
public static Map<String, Object> convert(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_EMAIL, StringUtils.isEmpty(user.getEmail()) ? "" : user.getEmail());
claims.put(TOKEN_USER_DISPLAY_NAME, user.getDisplayName());
claims.put(TOKEN_CREATE_TIME, System.currentTimeMillis());
claims.put(TOKEN_IS_ADMIN, user.getIsAdmin());
return claims;
}
}

View File

@@ -1,7 +1,7 @@
package com.tencent.supersonic.auth.api.authentication.request;
import jakarta.validation.constraints.NotBlank;
import javax.validation.constraints.NotBlank;
import lombok.Data;
@Data
@@ -13,6 +13,5 @@ public class UserReq {
@NotBlank(message = "password can not be null")
private String password;
@NotBlank(message = "password can not be null")
private String newPassword;
}

View File

@@ -1,14 +0,0 @@
package com.tencent.supersonic.auth.api.authentication.request;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class UserTokenReq {
@NotBlank(message = "name can not be null")
private String name;
@NotBlank(message = "expireTime can not be null")
private long expireTime;
}

View File

@@ -1,21 +1,17 @@
package com.tencent.supersonic.auth.api.authentication.service;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
import com.tencent.supersonic.common.pojo.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Set;
public interface UserService {
User getCurrentUser(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse);
User getCurrentUser(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse);
List<String> getUserNames();
@@ -23,27 +19,11 @@ public interface UserService {
void register(UserReq userCmd);
void deleteUser(long userId);
String login(UserReq userCmd, HttpServletRequest request);
String login(UserReq userCmd, String appKey);
String login(UserReq userCmd);
Set<String> getUserAllOrgId(String userName);
List<User> getUserByOrg(String key);
List<Organization> getOrganizationTree();
String getPassword(String userName);
void resetPassword(String userName, String password, String newPassword);
UserToken generateToken(String name, String userName, long expireTime);
List<UserToken> getUserTokens(String userName);
UserToken getUserToken(Long id);
void deleteUserToken(Long id);
}

View File

@@ -1,16 +1,14 @@
package com.tencent.supersonic.auth.api.authentication.service;
import com.tencent.supersonic.common.pojo.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface UserStrategy {
String getStrategyName();
boolean accept(boolean isEnableAuthentication);
User findUser(HttpServletRequest request, HttpServletResponse response);
User findUser(String token, String appKey);
}

View File

@@ -1,14 +1,15 @@
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 com.tencent.supersonic.common.config.SystemConfig;
import com.tencent.supersonic.common.pojo.User;
import com.tencent.supersonic.common.service.SystemConfigService;
import com.tencent.supersonic.common.pojo.SysParameter;
import com.tencent.supersonic.common.service.SysParameterService;
import com.tencent.supersonic.common.util.ContextUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.CollectionUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public final class UserHolder {
private static UserStrategy REPO;
@@ -19,21 +20,13 @@ public final class UserHolder {
public static User findUser(HttpServletRequest request, HttpServletResponse response) {
User user = REPO.findUser(request, response);
return getUser(user);
}
public static User findUser(String token, String appKey) {
User user = REPO.findUser(token, appKey);
return getUser(user);
}
private static User getUser(User user) {
SystemConfigService sysParameterService = ContextUtils.getBean(SystemConfigService.class);
SystemConfig systemConfig = sysParameterService.getSystemConfig();
if (!CollectionUtils.isEmpty(systemConfig.getAdmins())
&& systemConfig.getAdmins().contains(user.getName())) {
SysParameterService sysParameterService = ContextUtils.getBean(SysParameterService.class);
SysParameter sysParameter = sysParameterService.getSysParameter();
if (!CollectionUtils.isEmpty(sysParameter.getAdmins())
&& sysParameter.getAdmins().contains(user.getName())) {
user.setIsAdmin(1);
}
return user;
}
}

View File

@@ -1,8 +1,7 @@
package com.tencent.supersonic.auth.api.authorization.pojo;
import lombok.Data;
import java.util.List;
import lombok.Data;
@Data
public class AuthGroup {
@@ -11,12 +10,18 @@ public class AuthGroup {
private String name;
private Integer groupId;
private List<AuthRule> authRules;
/** row permission expression */
/**
* row permission expression
*/
private List<String> dimensionFilters;
/** row permission expression description information */
/**
* row permission expression description information
*/
private String dimensionFilterDescription;
private List<String> authorizedUsers;
/** authorization Department Id */
/**
* authorization Department Id
*/
private List<String> authorizedDepartmentIds;
}

View File

@@ -10,7 +10,8 @@ public class AuthRes {
private Long modelId;
private String name;
public AuthRes() {}
public AuthRes() {
}
public AuthRes(Long modelId, String name) {
this.modelId = modelId;

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

@@ -1,10 +1,9 @@
package com.tencent.supersonic.auth.api.authorization.pojo;
import lombok.Data;
import java.beans.Transient;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class AuthRule {

View File

@@ -1,9 +1,8 @@
package com.tencent.supersonic.auth.api.authorization.pojo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class DimensionFilter {

View File

@@ -1,8 +1,7 @@
package com.tencent.supersonic.auth.api.authorization.request;
import lombok.Data;
import java.util.List;
import lombok.Data;
@Data
public class AddUsersToGroupReq {

View File

@@ -1,6 +1,7 @@
package com.tencent.supersonic.auth.api.authorization.request;
import com.google.common.collect.Lists;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes;
import lombok.Data;
import lombok.ToString;
import org.springframework.util.CollectionUtils;
@@ -14,6 +15,8 @@ public class QueryAuthResReq {
private List<String> departmentIds = new ArrayList<>();
private List<AuthRes> resources;
private Long modelId;
private List<Long> modelIds;

View File

@@ -1,9 +1,9 @@
package com.tencent.supersonic.auth.api.authorization.request;
import com.tencent.supersonic.common.pojo.PageBaseReq;
import lombok.Data;
import com.tencent.supersonic.common.pojo.PageBaseReq;
import java.util.List;
import lombok.Data;
@Data
public class QueryGroupReq extends PageBaseReq {

View File

@@ -1,8 +1,7 @@
package com.tencent.supersonic.auth.api.authorization.request;
import lombok.Data;
import java.util.List;
import lombok.Data;
@Data
public class RemoveGroupReq {

View File

@@ -1,8 +1,7 @@
package com.tencent.supersonic.auth.api.authorization.request;
import lombok.Data;
import java.util.List;
import lombok.Data;
@Data
public class RemoveUsersFromGroupReq {

View File

@@ -1,16 +1,16 @@
package com.tencent.supersonic.auth.api.authorization.response;
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 lombok.Data;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class AuthorizedResourceResp {
private List<AuthRes> authResList = new ArrayList<>();
private List<AuthResGrp> resources = new ArrayList<>();
private List<DimensionFilter> filters = new ArrayList<>();
}

View File

@@ -1,10 +1,9 @@
package com.tencent.supersonic.auth.api.authorization.service;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup;
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
import com.tencent.supersonic.common.pojo.User;
import java.util.List;
public interface AuthService {

View File

@@ -38,6 +38,10 @@
<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>
@@ -46,6 +50,7 @@
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
<scope>compile</scope>
</dependency>

View File

@@ -4,31 +4,21 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.tencent.supersonic.auth.api.authentication.adaptor.UserAdaptor;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
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.authentication.persistence.dataobject.UserDO;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository;
import com.tencent.supersonic.auth.authentication.utils.TokenService;
import com.tencent.supersonic.common.pojo.User;
import com.tencent.supersonic.common.util.AESEncryptionUtil;
import com.tencent.supersonic.auth.authentication.utils.UserTokenUtils;
import com.tencent.supersonic.common.util.ContextUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* DefaultUserAdaptor provides a default method to obtain user and organization information
*/
@Slf4j
public class DefaultUserAdaptor implements UserAdaptor {
private List<UserDO> getUserDOList() {
@@ -54,14 +44,14 @@ public class DefaultUserAdaptor implements UserAdaptor {
@Override
public List<Organization> getOrganizationTree() {
Organization superSonic =
new Organization("1", "0", "SuperSonic", "SuperSonic", Lists.newArrayList(), true);
Organization hr =
new Organization("2", "1", "Hr", "SuperSonic/Hr", Lists.newArrayList(), false);
Organization sales = new Organization("3", "1", "Sales", "SuperSonic/Sales",
Lists.newArrayList(), false);
Organization marketing = new Organization("4", "1", "Marketing", "SuperSonic/Marketing",
Lists.newArrayList(), false);
Organization superSonic = new Organization("1", "0",
"SuperSonic", "SuperSonic", Lists.newArrayList(), true);
Organization hr = new Organization("2", "1",
"Hr", "SuperSonic/Hr", Lists.newArrayList(), false);
Organization sales = new Organization("3", "1",
"Sales", "SuperSonic/Sales", Lists.newArrayList(), false);
Organization marketing = new Organization("4", "1",
"Marketing", "SuperSonic/Marketing", Lists.newArrayList(), false);
List<Organization> subOrganization = Lists.newArrayList(hr, sales, marketing);
superSonic.setSubOrganizations(subOrganization);
return Lists.newArrayList(superSonic);
@@ -82,124 +72,22 @@ public class DefaultUserAdaptor implements UserAdaptor {
}
UserDO userDO = new UserDO();
BeanUtils.copyProperties(userReq, userDO);
try {
byte[] salt = AESEncryptionUtil.generateSalt(userDO.getName());
userDO.setSalt(AESEncryptionUtil.getStringFromBytes(salt));
userDO.setPassword(AESEncryptionUtil.encrypt(userReq.getPassword(), salt));
} catch (Exception e) {
throw new RuntimeException("password encrypt error, please try again");
}
userRepository.addUser(userDO);
}
@Override
public void deleteUser(long userId) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
userRepository.deleteUser(userId);
}
@Override
public String login(UserReq userReq, HttpServletRequest request) {
TokenService tokenService = ContextUtils.getBean(TokenService.class);
String appKey = tokenService.getAppKey(request);
return login(userReq, appKey);
}
@Override
public String login(UserReq userReq, String appKey) {
TokenService tokenService = ContextUtils.getBean(TokenService.class);
try {
UserWithPassword user = getUserWithPassword(userReq);
String token = tokenService.generateToken(UserWithPassword.convert(user), appKey);
updateLastLogin(userReq.getName());
return token;
} catch (Exception e) {
log.error("", e);
throw new RuntimeException("password encrypt error, please try again");
}
}
@Override
public String getPassword(String userName) {
UserDO userDO = getUser(userName);
if (userDO == null) {
throw new RuntimeException("user not exist,please register");
}
return userDO.getPassword();
}
@Override
public void resetPassword(String userName, String password, String newPassword) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
Optional<UserDO> userDOOptional = Optional.ofNullable(getUser(userName));
UserDO userDO = userDOOptional
.orElseThrow(() -> new RuntimeException("User does not exist, please register"));
try {
validateOldPassword(userDO, password);
updatePassword(userDO, newPassword, userRepository);
} catch (PasswordEncryptionException e) {
throw new RuntimeException("Password encryption error, please try again", e);
}
}
private void validateOldPassword(UserDO userDO, String password)
throws PasswordEncryptionException {
String oldPassword = encryptPassword(password, userDO.getSalt());
if (!userDO.getPassword().equals(oldPassword)) {
throw new RuntimeException("Old password is not correct, please try again");
}
}
private void updatePassword(UserDO userDO, String newPassword, UserRepository userRepository)
throws PasswordEncryptionException {
try {
byte[] salt = AESEncryptionUtil.generateSalt(userDO.getName());
userDO.setSalt(AESEncryptionUtil.getStringFromBytes(salt));
userDO.setPassword(AESEncryptionUtil.encrypt(newPassword, salt));
userRepository.updateUser(userDO);
} catch (Exception e) {
throw new PasswordEncryptionException("Error encrypting password", e);
}
}
private String encryptPassword(String password, String salt)
throws PasswordEncryptionException {
try {
return AESEncryptionUtil.encrypt(password, AESEncryptionUtil.getBytesFromString(salt));
} catch (Exception e) {
throw new PasswordEncryptionException("Error encrypting password", e);
}
}
public static class PasswordEncryptionException extends Exception {
public PasswordEncryptionException(String message, Throwable cause) {
super(message, cause);
}
}
private UserWithPassword getUserWithPassword(UserReq userReq) {
public String login(UserReq userReq) {
UserTokenUtils userTokenUtils = ContextUtils.getBean(UserTokenUtils.class);
UserDO userDO = getUser(userReq.getName());
if (userDO == null) {
throw new RuntimeException("user not exist,please register");
}
try {
String password = AESEncryptionUtil.encrypt(userReq.getPassword(),
AESEncryptionUtil.getBytesFromString(userDO.getSalt()));
if (userDO.getPassword().equals(password)) {
UserWithPassword user = UserWithPassword.get(userDO.getId(), userDO.getName(),
userDO.getDisplayName(), userDO.getEmail(), userDO.getPassword(),
userDO.getIsAdmin());
return user;
} else {
throw new RuntimeException("password not correct, please try again");
}
} catch (Exception e) {
throw new RuntimeException("password encrypt error, please try again");
if (userDO.getPassword().equals(userReq.getPassword())) {
UserWithPassword user = UserWithPassword.get(userDO.getId(), userDO.getName(), userDO.getDisplayName(),
userDO.getEmail(), userDO.getPassword(), userDO.getIsAdmin());
return userTokenUtils.generateToken(user);
}
throw new RuntimeException("password not correct, please try again");
}
@Override
@@ -212,77 +100,4 @@ public class DefaultUserAdaptor implements UserAdaptor {
return Sets.newHashSet();
}
@Override
public UserToken generateToken(String name, String userName, long expireTime) {
TokenService tokenService = ContextUtils.getBean(TokenService.class);
UserDO userDO = getUser(userName);
if (userDO == null) {
throw new RuntimeException("user not exist,please register");
}
UserWithPassword userWithPassword =
new UserWithPassword(userDO.getId(), userDO.getName(), userDO.getDisplayName(),
userDO.getEmail(), userDO.getPassword(), userDO.getIsAdmin());
// 使用令牌名称作为生成key 这样可以区分正常请求和api 请求api 的令牌失效时间很长,需考虑令牌泄露的情况
String token = tokenService.generateToken(UserWithPassword.convert(userWithPassword),
"SysDbToken:" + name, (new Date().getTime() + expireTime));
UserTokenDO userTokenDO = saveUserToken(name, userName, token, expireTime);
return convertUserToken(userTokenDO);
}
@Override
public void deleteUserToken(Long id) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
userRepository.deleteUserToken(id);
}
@Override
public UserToken getUserToken(Long id) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
return convertUserToken(userRepository.getUserToken(id));
}
@Override
public List<UserToken> getUserTokens(String userName) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
List<UserToken> userTokens = userRepository.getUserTokenListByName(userName).stream()
.map(this::convertUserToken).collect(Collectors.toList());
return userTokens;
}
private UserTokenDO saveUserToken(String tokenName, String userName, String token,
long expireTime) {
UserTokenDO userTokenDO = new UserTokenDO();
userTokenDO.setName(tokenName);
userTokenDO.setUserName(userName);
userTokenDO.setToken(token);
userTokenDO.setExpireTime(expireTime);
userTokenDO.setCreateTime(new java.util.Date());
userTokenDO.setCreateBy(userName);
userTokenDO.setUpdateBy(userName);
userTokenDO.setExpireDateTime(new java.util.Date(System.currentTimeMillis() + expireTime));
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
userRepository.addUserToken(userTokenDO);
return userTokenDO;
}
private UserToken convertUserToken(UserTokenDO userTokenDO) {
UserToken userToken = new UserToken();
userToken.setId(userTokenDO.getId());
userToken.setName(userTokenDO.getName());
userToken.setUserName(userTokenDO.getUserName());
userToken.setToken(userTokenDO.getToken());
userToken.setExpireTime(userTokenDO.getExpireTime());
userToken.setCreateDate(userTokenDO.getCreateTime());
userToken.setExpireDate(userTokenDO.getExpireDateTime());
return userToken;
}
private void updateLastLogin(String userName) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
UserDO userDO = userRepository.getUser(userName);
userDO.setLastLogin(new Timestamp(System.currentTimeMillis()));
userRepository.updateUser(userDO);
}
}

View File

@@ -1,4 +1,5 @@
package com.tencent.supersonic.auth.api.authentication.annotation;
package com.tencent.supersonic.auth.authentication.interceptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -8,4 +9,5 @@ import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthenticationIgnore {
}

View File

@@ -1,28 +1,38 @@
package com.tencent.supersonic.auth.authentication.interceptor;
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
import com.tencent.supersonic.auth.api.authentication.service.UserService;
import com.tencent.supersonic.auth.authentication.utils.TokenService;
import lombok.extern.slf4j.Slf4j;
import com.tencent.supersonic.auth.api.authentication.constant.UserConstants;
import com.tencent.supersonic.auth.authentication.service.UserServiceImpl;
import com.tencent.supersonic.auth.authentication.utils.UserTokenUtils;
import com.tencent.supersonic.common.util.S2ThreadContext;
import org.apache.catalina.connector.RequestFacade;
import org.apache.commons.lang3.StringUtils;
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;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
@Slf4j
public abstract class AuthenticationInterceptor implements HandlerInterceptor {
protected AuthenticationConfig authenticationConfig;
protected UserService userService;
protected UserServiceImpl userServiceImpl;
protected TokenService tokenService;
protected UserTokenUtils userTokenUtils;
protected S2ThreadContext s2ThreadContext;
protected boolean isExcludedUri(String uri) {
String excludePathStr = authenticationConfig.getExcludePath();
if (StringUtils.isEmpty(excludePathStr)) {
if (Strings.isEmpty(excludePathStr)) {
return false;
}
List<String> excludePaths = Arrays.asList(excludePathStr.split(","));
@@ -34,7 +44,7 @@ public abstract class AuthenticationInterceptor implements HandlerInterceptor {
protected boolean isIncludedUri(String uri) {
String includePathStr = authenticationConfig.getIncludePath();
if (StringUtils.isEmpty(includePathStr)) {
if (Strings.isEmpty(includePathStr)) {
return false;
}
List<String> includePaths = Arrays.asList(includePathStr.split(","));
@@ -44,4 +54,51 @@ public abstract class AuthenticationInterceptor implements HandlerInterceptor {
return includePaths.stream().anyMatch(uri::startsWith);
}
protected boolean isInternalRequest(HttpServletRequest request) {
String internal = request.getHeader(UserConstants.INTERNAL);
return "true".equalsIgnoreCase(internal);
}
protected boolean isAppRequest(HttpServletRequest request) {
String appId = request.getHeader(authenticationConfig.getAppId());
return StringUtils.isNotBlank(appId);
}
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

@@ -1,36 +1,44 @@
package com.tencent.supersonic.auth.authentication.interceptor;
import com.tencent.supersonic.auth.api.authentication.annotation.AuthenticationIgnore;
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.auth.api.authentication.service.UserService;
import com.tencent.supersonic.auth.authentication.utils.TokenService;
import com.tencent.supersonic.auth.authentication.service.UserServiceImpl;
import com.tencent.supersonic.auth.authentication.utils.UserTokenUtils;
import com.tencent.supersonic.common.pojo.exception.AccessException;
import com.tencent.supersonic.common.util.ContextUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.tencent.supersonic.common.util.S2ThreadContext;
import com.tencent.supersonic.common.util.ThreadContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.method.HandlerMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Optional;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.*;
@Slf4j
public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws AccessException {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws AccessException {
authenticationConfig = ContextUtils.getBean(AuthenticationConfig.class);
userService = ContextUtils.getBean(UserService.class);
tokenService = ContextUtils.getBean(TokenService.class);
userServiceImpl = ContextUtils.getBean(UserServiceImpl.class);
userTokenUtils = ContextUtils.getBean(UserTokenUtils.class);
s2ThreadContext = ContextUtils.getBean(S2ThreadContext.class);
if (!authenticationConfig.isEnabled()) {
setFakerUser(request);
return true;
}
if (isInternalRequest(request)) {
setFakerUser(request);
return true;
}
if (isAppRequest(request)) {
setFakerUser(request);
return true;
}
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
@@ -49,26 +57,26 @@ public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor
return true;
}
UserWithPassword user = getUserWithPassword(request);
if (user != null) {
UserWithPassword user = userTokenUtils.getUserWithPassword(request);
if (StringUtils.isNotBlank(user.getName())) {
setContext(user.getName(), request);
return true;
}
throw new AccessException("authentication failed, please login");
}
public UserWithPassword getUserWithPassword(HttpServletRequest request) {
final Optional<Claims> claimsOptional = tokenService.getClaims(request);
if (!claimsOptional.isPresent()) {
return null;
}
Claims claims = claimsOptional.get();
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));
Integer isAdmin = claims.get(TOKEN_IS_ADMIN) == null ? 0
: Integer.parseInt(claims.get(TOKEN_IS_ADMIN).toString());
return UserWithPassword.get(userId, userName, displayName, email, password, isAdmin);
private void setFakerUser(HttpServletRequest request) {
String token = userTokenUtils.generateAdminToken();
reflectSetparam(request, authenticationConfig.getTokenHttpHeaderKey(), token);
setContext(User.getFakeUser().getName(), request);
}
private void setContext(String userName, HttpServletRequest request) {
ThreadContext threadContext = ThreadContext.builder()
.token(request.getHeader(authenticationConfig.getTokenHttpHeaderKey()))
.userName(userName)
.build();
s2ThreadContext.set(threadContext);
}
}

View File

@@ -10,11 +10,12 @@ import java.util.List;
@Configuration
public class InterceptorFactory implements WebMvcConfigurer {
private List<AuthenticationInterceptor> authenticationInterceptors;
public InterceptorFactory() {
authenticationInterceptors = SpringFactoriesLoader.loadFactories(
AuthenticationInterceptor.class, Thread.currentThread().getContextClassLoader());
authenticationInterceptors = SpringFactoriesLoader.loadFactories(AuthenticationInterceptor.class,
Thread.currentThread().getContextClassLoader());
}
@Override
@@ -24,4 +25,5 @@ public class InterceptorFactory implements WebMvcConfigurer {
.excludePathPatterns("/", "/webapp/**", "/error");
}
}
}

View File

@@ -1,55 +1,129 @@
package com.tencent.supersonic.auth.authentication.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.sql.Timestamp;
@Data
@TableName("s2_user")
public class UserDO {
@TableId(type = IdType.AUTO)
/**
*
*/
private Long id;
/** */
/**
*
*/
private String name;
/** */
/**
*
*/
private String password;
private String salt;
/** */
/**
*
*/
private String displayName;
/** */
/**
*
*/
private String email;
/** */
/**
*
*/
private Integer isAdmin;
private Timestamp lastLogin;
/**
*
* @return id
*/
public Long getId() {
return id;
}
/** @param name */
/**
*
* @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();
}
/** @param password */
/**
*
* @return password
*/
public String getPassword() {
return password;
}
/**
*
* @param password
*/
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
}
public void setSalt(String salt) {
this.salt = salt == null ? null : salt.trim();
/**
*
* @return display_name
*/
public String getDisplayName() {
return displayName;
}
/** @param email */
/**
*
* @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();
}
/**
*
* @return is_admin
*/
public Integer getIsAdmin() {
return isAdmin;
}
/**
*
* @param isAdmin
*/
public void setIsAdmin(Integer isAdmin) {
this.isAdmin = isAdmin;
}
}

View File

@@ -4,64 +4,101 @@ import java.util.ArrayList;
import java.util.List;
public class UserDOExample {
/** s2_user */
/**
* s2_user
*/
protected String orderByClause;
/** s2_user */
/**
* s2_user
*/
protected boolean distinct;
/** s2_user */
/**
* s2_user
*/
protected List<Criteria> oredCriteria;
/** s2_user */
/**
* s2_user
*/
protected Integer limitStart;
/** s2_user */
/**
* s2_user
*/
protected Integer limitEnd;
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public UserDOExample() {
oredCriteria = new ArrayList<Criteria>();
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public String getOrderByClause() {
return orderByClause;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public boolean isDistinct() {
return distinct;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public List<Criteria> getOredCriteria() {
return oredCriteria;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public void or(Criteria criteria) {
oredCriteria.add(criteria);
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public Criteria or() {
Criteria criteria = createCriteriaInternal();
oredCriteria.add(criteria);
return criteria;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public Criteria createCriteria() {
Criteria criteria = createCriteriaInternal();
if (oredCriteria.size() == 0) {
@@ -70,40 +107,60 @@ public class UserDOExample {
return criteria;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
protected Criteria createCriteriaInternal() {
Criteria criteria = new Criteria();
return criteria;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public void clear() {
oredCriteria.clear();
orderByClause = null;
distinct = false;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public void setLimitStart(Integer limitStart) {
this.limitStart = limitStart;
this.limitStart=limitStart;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public Integer getLimitStart() {
return limitStart;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public void setLimitEnd(Integer limitEnd) {
this.limitEnd = limitEnd;
this.limitEnd=limitEnd;
}
/** @mbg.generated */
/**
*
* @mbg.generated
*/
public Integer getLimitEnd() {
return limitEnd;
}
/** s2_user null */
/**
* s2_user null
*/
protected abstract static class GeneratedCriteria {
protected List<Criterion> criteria;
@@ -138,8 +195,7 @@ public class UserDOExample {
criteria.add(new Criterion(condition, value));
}
protected void addCriterion(String condition, Object value1, Object value2,
String property) {
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");
}
@@ -547,7 +603,9 @@ public class UserDOExample {
}
}
/** s2_user */
/**
* s2_user
*/
public static class Criteria extends GeneratedCriteria {
protected Criteria() {
@@ -555,7 +613,9 @@ public class UserDOExample {
}
}
/** s2_user null */
/**
* s2_user null
*/
public static class Criterion {
private String condition;
@@ -628,8 +688,7 @@ public class UserDOExample {
this(condition, value, null);
}
protected Criterion(String condition, Object value, Object secondValue,
String typeHandler) {
protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
super();
this.condition = condition;
this.value = value;

View File

@@ -1,25 +0,0 @@
package com.tencent.supersonic.auth.authentication.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@Data
@TableName("s2_user_token")
public class UserTokenDO {
@TableId(type = IdType.AUTO)
Integer id;
String name;
String userName;
Long expireTime;
String token;
String salt;
Date createTime;
Date updateTime;
String createBy;
String updateBy;
Date expireDateTime;
}

View File

@@ -1,17 +1,22 @@
package com.tencent.supersonic.auth.authentication.persistence.mapper;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDOExample;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserDOMapper extends BaseMapper<UserDO> {
public interface UserDOMapper {
/**
* @mbg.generated
*/
int insert(UserDO record);
/**
* @mbg.generated
*/
List<UserDO> selectByExample(UserDOExample example);
void updateByPrimaryKey(UserDO userDO);
}

View File

@@ -1,11 +0,0 @@
package com.tencent.supersonic.auth.authentication.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserTokenDOMapper extends BaseMapper<UserTokenDO> {
}

View File

@@ -0,0 +1,42 @@
package com.tencent.supersonic.auth.authentication.persistence.repository.impl;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDOExample;
import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository;
import com.tencent.supersonic.auth.authentication.persistence.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

@@ -1,8 +1,6 @@
package com.tencent.supersonic.auth.authentication.persistence.repository;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import java.util.List;
public interface UserRepository {
@@ -11,21 +9,5 @@ public interface UserRepository {
void addUser(UserDO userDO);
List<UserTokenDO> getUserTokenListByName(String userName);
UserDO getUser(String name);
void updateUser(UserDO userDO);
void addUserToken(UserTokenDO userTokenDO);
UserTokenDO getUserToken(Long tokenId);
UserTokenDO getUserTokenByName(String tokenName);
void deleteUserTokenByName(String userName);
void deleteUserToken(Long tokenId);
void deleteUser(long userId);
}

View File

@@ -1,91 +0,0 @@
package com.tencent.supersonic.auth.authentication.persistence.repository.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDOExample;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import com.tencent.supersonic.auth.authentication.persistence.mapper.UserDOMapper;
import com.tencent.supersonic.auth.authentication.persistence.mapper.UserTokenDOMapper;
import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class UserRepositoryImpl implements UserRepository {
private UserDOMapper userDOMapper;
private UserTokenDOMapper userTokenDOMapper;
public UserRepositoryImpl(UserDOMapper userDOMapper, UserTokenDOMapper userTokenDOMapper) {
this.userDOMapper = userDOMapper;
this.userTokenDOMapper = userTokenDOMapper;
}
@Override
public List<UserDO> getUserList() {
return userDOMapper.selectByExample(new UserDOExample());
}
@Override
public void updateUser(UserDO userDO) {
userDOMapper.updateByPrimaryKey(userDO);
}
@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);
}
@Override
public List<UserTokenDO> getUserTokenListByName(String userName) {
QueryWrapper<UserTokenDO> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserTokenDO::getUserName, userName);
return userTokenDOMapper.selectList(queryWrapper);
}
@Override
public void addUserToken(UserTokenDO userTokenDO) {
userTokenDOMapper.insert(userTokenDO);
}
@Override
public UserTokenDO getUserToken(Long tokenId) {
return userTokenDOMapper.selectById(tokenId);
}
@Override
public UserTokenDO getUserTokenByName(String tokenName) {
QueryWrapper<UserTokenDO> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserTokenDO::getName, tokenName);
return userTokenDOMapper.selectOne(queryWrapper);
}
@Override
public void deleteUserTokenByName(String userName) {
QueryWrapper<UserTokenDO> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserTokenDO::getUserName, userName);
userTokenDOMapper.delete(queryWrapper);
}
@Override
public void deleteUserToken(Long tokenId) {
userTokenDOMapper.deleteById(tokenId);
}
@Override
public void deleteUser(long userId) {
userDOMapper.deleteById(userId);
}
}

View File

@@ -1,16 +1,20 @@
package com.tencent.supersonic.auth.authentication.rest;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
import com.tencent.supersonic.auth.api.authentication.request.UserTokenReq;
import com.tencent.supersonic.auth.api.authentication.service.UserService;
import com.tencent.supersonic.common.pojo.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Set;
@@ -26,8 +30,7 @@ public class UserController {
}
@GetMapping("/getCurrentUser")
public User getCurrentUser(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
public User getCurrentUser(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
return userService.getCurrentUser(httpServletRequest, httpServletResponse);
}
@@ -61,49 +64,9 @@ public class UserController {
userService.register(userCmd);
}
@DeleteMapping("/delete/{userId}")
public void delete(@PathVariable("userId") long userId, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws IllegalAccessException {
User user = userService.getCurrentUser(httpServletRequest, httpServletResponse);
if (user.getIsAdmin() != 1) {
throw new IllegalAccessException("only admin can delete user");
}
userService.deleteUser(userId);
}
@PostMapping("/login")
public String login(@RequestBody UserReq userCmd, HttpServletRequest request) {
return userService.login(userCmd, request);
public String login(@RequestBody UserReq userCmd) {
return userService.login(userCmd);
}
@PostMapping("/resetPassword")
public void resetPassword(@RequestBody UserReq userCmd, HttpServletRequest request,
HttpServletResponse response) {
User user = userService.getCurrentUser(request, response);
userService.resetPassword(user.getName(), userCmd.getPassword(), userCmd.getNewPassword());
}
@PostMapping("/generateToken")
public UserToken generateToken(@RequestBody UserTokenReq userTokenReq,
HttpServletRequest request, HttpServletResponse response) {
User user = userService.getCurrentUser(request, response);
return userService.generateToken(userTokenReq.getName(), user.getName(),
userTokenReq.getExpireTime());
}
@GetMapping("/getUserTokens")
public List<UserToken> getUserTokens(HttpServletRequest request, HttpServletResponse response) {
User user = userService.getCurrentUser(request, response);
return userService.getUserTokens(user.getName());
}
@GetMapping("/getUserToken")
public UserToken getUserToken(@RequestParam(name = "tokenId") Long tokenId) {
return userService.getUserToken(tokenId);
}
@PostMapping("/deleteUserToken")
public void deleteUserToken(@RequestParam(name = "tokenId") Long tokenId) {
userService.deleteUserToken(tokenId);
}
}

View File

@@ -1,39 +1,36 @@
package com.tencent.supersonic.auth.authentication.service;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
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 com.tencent.supersonic.auth.authentication.utils.ComponentFactory;
import com.tencent.supersonic.common.config.SystemConfig;
import com.tencent.supersonic.common.pojo.User;
import com.tencent.supersonic.common.service.SystemConfigService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.tencent.supersonic.common.pojo.SysParameter;
import com.tencent.supersonic.common.service.SysParameterService;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Set;
@Service
public class UserServiceImpl implements UserService {
private SystemConfigService sysParameterService;
private SysParameterService sysParameterService;
public UserServiceImpl(SystemConfigService sysParameterService) {
public UserServiceImpl(SysParameterService sysParameterService) {
this.sysParameterService = sysParameterService;
}
@Override
public User getCurrentUser(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
public User getCurrentUser(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
User user = UserHolder.findUser(httpServletRequest, httpServletResponse);
if (user != null) {
SystemConfig systemConfig = sysParameterService.getSystemConfig();
if (!CollectionUtils.isEmpty(systemConfig.getAdmins())
&& systemConfig.getAdmins().contains(user.getName())) {
SysParameter sysParameter = sysParameterService.getSysParameter();
if (!CollectionUtils.isEmpty(sysParameter.getAdmins())
&& sysParameter.getAdmins().contains(user.getName())) {
user.setIsAdmin(1);
}
}
@@ -71,47 +68,8 @@ public class UserServiceImpl implements UserService {
}
@Override
public void deleteUser(long userId) {
ComponentFactory.getUserAdaptor().deleteUser(userId);
public String login(UserReq userReq) {
return ComponentFactory.getUserAdaptor().login(userReq);
}
@Override
public String login(UserReq userReq, HttpServletRequest request) {
return ComponentFactory.getUserAdaptor().login(userReq, request);
}
@Override
public String login(UserReq userReq, String appKey) {
return ComponentFactory.getUserAdaptor().login(userReq, appKey);
}
@Override
public String getPassword(String userName) {
return ComponentFactory.getUserAdaptor().getPassword(userName);
}
@Override
public void resetPassword(String userName, String password, String newPassword) {
ComponentFactory.getUserAdaptor().resetPassword(userName, password, newPassword);
}
@Override
public UserToken generateToken(String name, String userName, long expireTime) {
return ComponentFactory.getUserAdaptor().generateToken(name, userName, expireTime);
}
@Override
public List<UserToken> getUserTokens(String userName) {
return ComponentFactory.getUserAdaptor().getUserTokens(userName);
}
@Override
public UserToken getUserToken(Long id) {
return ComponentFactory.getUserAdaptor().getUserToken(id);
}
@Override
public void deleteUserToken(Long id) {
ComponentFactory.getUserAdaptor().deleteUserToken(id);
}
}

View File

@@ -1,21 +1,15 @@
package com.tencent.supersonic.auth.authentication.strategy;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.service.UserStrategy;
import com.tencent.supersonic.common.pojo.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
@Service
public class FakeUserStrategy implements UserStrategy {
public static final String STRATEGY_NAME = "fake";
@Override
public String getStrategyName() {
return STRATEGY_NAME;
}
@Override
public boolean accept(boolean isEnableAuthentication) {
return !isEnableAuthentication;
@@ -23,11 +17,7 @@ public class FakeUserStrategy implements UserStrategy {
@Override
public User findUser(HttpServletRequest request, HttpServletResponse response) {
return User.getDefaultUser();
return User.getFakeUser();
}
@Override
public User findUser(String token, String appKey) {
return User.getDefaultUser();
}
}

View File

@@ -1,29 +1,22 @@
package com.tencent.supersonic.auth.authentication.strategy;
import com.tencent.supersonic.auth.api.authentication.constant.UserConstants;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.service.UserStrategy;
import com.tencent.supersonic.auth.authentication.utils.TokenService;
import com.tencent.supersonic.common.pojo.User;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.tencent.supersonic.auth.authentication.utils.UserTokenUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class HttpHeaderUserStrategy implements UserStrategy {
public static final String STRATEGY_NAME = "http";
private final TokenService tokenService;
public HttpHeaderUserStrategy(TokenService tokenService) {
this.tokenService = tokenService;
}
private final UserTokenUtils userTokenUtils;
@Override
public String getStrategyName() {
return STRATEGY_NAME;
public HttpHeaderUserStrategy(UserTokenUtils userTokenUtils) {
this.userTokenUtils = userTokenUtils;
}
@Override
@@ -33,33 +26,6 @@ public class HttpHeaderUserStrategy implements UserStrategy {
@Override
public User findUser(HttpServletRequest request, HttpServletResponse response) {
return getUser(request);
return userTokenUtils.getUser(request);
}
@Override
public User findUser(String token, String appKey) {
return getUser(token, appKey);
}
public User getUser(HttpServletRequest request) {
final Optional<Claims> claimsOptional = tokenService.getClaims(request);
return claimsOptional.map(this::getUser).orElse(User.getVisitUser());
}
public User getUser(String token, String appKey) {
final Optional<Claims> claimsOptional = tokenService.getClaims(token, appKey);
return claimsOptional.map(this::getUser).orElse(User.getVisitUser());
}
private User getUser(Claims claims) {
Long userId =
Long.parseLong(claims.getOrDefault(UserConstants.TOKEN_USER_ID, 0).toString());
String userName = String.valueOf(claims.get(UserConstants.TOKEN_USER_NAME));
String email = String.valueOf(claims.get(UserConstants.TOKEN_USER_EMAIL));
String displayName = String.valueOf(claims.get(UserConstants.TOKEN_USER_DISPLAY_NAME));
Integer isAdmin = claims.get(UserConstants.TOKEN_IS_ADMIN) == null ? 0
: Integer.parseInt(claims.get(UserConstants.TOKEN_IS_ADMIN).toString());
return User.get(userId, userName, displayName, email, isAdmin);
}
}

View File

@@ -4,49 +4,33 @@ package com.tencent.supersonic.auth.authentication.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 jakarta.annotation.PostConstruct;
import java.util.List;
import javax.annotation.PostConstruct;
import lombok.Data;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Optional;
@Configuration
@Data
public class UserStrategyFactory {
private List<UserStrategy> userStrategyList;
private AuthenticationConfig authenticationConfig;
public UserStrategyFactory(AuthenticationConfig authenticationConfig,
List<UserStrategy> userStrategyList) {
public UserStrategyFactory(AuthenticationConfig authenticationConfig, List<UserStrategy> userStrategyList) {
this.authenticationConfig = authenticationConfig;
this.userStrategyList = userStrategyList;
}
@PostConstruct
public void setUserStrategy() {
boolean enabled = authenticationConfig.isEnabled();
if (!enabled) {
for (UserStrategy userStrategy : userStrategyList) {
if (userStrategy.accept(authenticationConfig.isEnabled())) {
UserHolder.setStrategy(userStrategy);
}
for (UserStrategy userStrategy : userStrategyList) {
if (userStrategy.accept(authenticationConfig.isEnabled())) {
UserHolder.setStrategy(userStrategy);
}
return;
}
String strategy = authenticationConfig.getStrategy();
Optional<UserStrategy> strategyOptional = userStrategyList.stream()
.filter(t -> t.accept(true) && strategy.equalsIgnoreCase(t.getStrategyName()))
.findAny();
if (strategyOptional.isPresent()) {
UserHolder.setStrategy(strategyOptional.get());
} else {
throw new IllegalStateException("strategy is not found: " + strategy);
}
}
}

View File

@@ -2,7 +2,6 @@ package com.tencent.supersonic.auth.authentication.utils;
import com.tencent.supersonic.auth.api.authentication.adaptor.UserAdaptor;
import org.springframework.core.io.support.SpringFactoriesLoader;
import java.util.Objects;
public class ComponentFactory {
@@ -17,7 +16,8 @@ public class ComponentFactory {
}
private static <T> T init(Class<T> factoryType) {
return SpringFactoriesLoader
.loadFactories(factoryType, Thread.currentThread().getContextClassLoader()).get(0);
return SpringFactoriesLoader.loadFactories(factoryType,
Thread.currentThread().getContextClassLoader()).get(0);
}
}

View File

@@ -1,163 +0,0 @@
package com.tencent.supersonic.auth.authentication.utils;
import javax.crypto.spec.SecretKeySpec;
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository;
import com.tencent.supersonic.common.pojo.exception.AccessException;
import com.tencent.supersonic.common.util.ContextUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_PREFIX;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_NAME;
@Slf4j
@Component
public class TokenService {
private AuthenticationConfig authenticationConfig;
public TokenService(AuthenticationConfig authenticationConfig) {
this.authenticationConfig = authenticationConfig;
}
public String generateToken(Map<String, Object> claims, HttpServletRequest request) {
String appKey = getAppKey(request);
long expiration = System.currentTimeMillis() + authenticationConfig.getTokenTimeout();
return generateToken(claims, appKey, expiration);
}
public String generateToken(Map<String, Object> claims, long expiration) {
String appKey = authenticationConfig.getTokenDefaultAppKey();
long exp = System.currentTimeMillis() + expiration;
return generateToken(claims, appKey, exp);
}
public String generateToken(Map<String, Object> claims, String appKey) {
long expiration = System.currentTimeMillis() + authenticationConfig.getTokenTimeout();
return toTokenString(claims, appKey, expiration);
}
public String generateToken(Map<String, Object> claims, String appKey, long expiration) {
return toTokenString(claims, appKey, expiration);
}
public String generateAppUserToken(HttpServletRequest request) {
String appName = request.getHeader("AppId");
if (StringUtils.isBlank(appName)) {
String message = "AppId is blank, get app_user failed";
log.warn("{}, uri: {}", message, request.getServletPath());
throw new AccessException(message);
}
UserWithPassword appUser = new UserWithPassword(appName);
appUser.setId(1L);
appUser.setName(appName);
appUser.setPassword("c3VwZXJzb25pY0BiaWNvbdktJJYWw6A3rEmBUPzbn/6DNeYnD+y3mAwDKEMS3KVT");
appUser.setDisplayName(appName);
appUser.setIsAdmin(0);
return generateToken(UserWithPassword.convert(appUser), request);
}
public Optional<Claims> getClaims(HttpServletRequest request) {
String token = request.getHeader(authenticationConfig.getTokenHttpHeaderKey());
String appKey = getAppKey(request);
return getClaims(token, appKey);
}
private Optional<Claims> getClaims(String token, HttpServletRequest request) {
Optional<Claims> claims;
try {
String appKey = getAppKey(request);
claims = getClaims(token, appKey);
} catch (Exception e) {
throw new AccessException("parse user info from token failed :" + token);
}
return claims;
}
public Optional<Claims> getClaims(String token, String appKey) {
try {
if (StringUtils.isNotBlank(appKey) && appKey.startsWith("SysDbToken:")) {// 如果是配置的长期令牌,需校验数据库是否存在该配置
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
UserTokenDO dbToken =
userRepository.getUserTokenByName(appKey.substring("SysDbToken:".length()));
if (dbToken == null || !dbToken.getToken().equals(token.replace("Bearer ", ""))) {
throw new AccessException("Token does not exist :" + appKey);
}
}
String tokenSecret = getTokenSecret(appKey);
Claims claims =
Jwts.parser().setSigningKey(tokenSecret.getBytes(StandardCharsets.UTF_8))
.build().parseClaimsJws(getTokenString(token)).getBody();
return Optional.of(claims);
} catch (Exception e) {
log.info("can not getClaims from appKey:{} token:{}, please login", appKey, token);
}
return Optional.empty();
}
private static String getTokenString(String token) {
return token.startsWith(TOKEN_PREFIX)
? token.substring(token.indexOf(TOKEN_PREFIX) + TOKEN_PREFIX.length()).trim()
: token.trim();
}
private String toTokenString(Map<String, Object> claims, String appKey, long expiration) {
Date expirationDate = new Date(expiration);
String tokenSecret = getTokenSecret(appKey);
return Jwts.builder().setClaims(claims).setSubject(claims.get(TOKEN_USER_NAME).toString())
.setExpiration(expirationDate)
.signWith(new SecretKeySpec(tokenSecret.getBytes(StandardCharsets.UTF_8),
SignatureAlgorithm.HS512.getJcaName()), SignatureAlgorithm.HS512)
.compact();
}
private String getTokenSecret(String appKey) {
Map<String, String> appKeyToSecretMap = authenticationConfig.getAppKeyToSecretMap();
String secret = appKeyToSecretMap.get(appKey);
if (StringUtils.isBlank(secret)) {
if (StringUtils.isNotBlank(appKey) && appKey.startsWith("SysDbToken:")) { // 是配置的长期令牌
String realAppKey = appKey.substring("SysDbToken:".length());
String tmp =
"WIaO9YRRVt+7QtpPvyWsARFngnEcbaKBk783uGFwMrbJBaochsqCH62L4Kijcb0sZCYoSsiKGV/zPml5MnZ3uQ==";
if (tmp.length() <= realAppKey.length()) {
return realAppKey;
} else {
return realAppKey + tmp.substring(realAppKey.length());
}
}
throw new AccessException("get secret from appKey failed :" + appKey);
}
return secret;
}
public String getAppKey(HttpServletRequest request) {
String appKey = request.getHeader(authenticationConfig.getTokenHttpHeaderAppKey());
if (StringUtils.isBlank(appKey)) {
appKey = authenticationConfig.getTokenDefaultAppKey();
}
return appKey;
}
public String getDefaultAppKey() {
return authenticationConfig.getTokenDefaultAppKey();
}
}

View File

@@ -0,0 +1,122 @@
package com.tencent.supersonic.auth.authentication.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_IS_ADMIN;
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.pojo.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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@Slf4j
@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());
claims.put(TOKEN_IS_ADMIN, user.getIsAdmin());
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());
claims.put(TOKEN_IS_ADMIN, 1);
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));
Integer isAdmin = claims.get(TOKEN_IS_ADMIN) == null
? 0 : Integer.parseInt(claims.get(TOKEN_IS_ADMIN).toString());
return User.get(userId, userName, displayName, email, isAdmin);
}
public UserWithPassword getUserWithPassword(HttpServletRequest request) {
String token = request.getHeader(authenticationConfig.getTokenHttpHeaderKey());
if (StringUtils.isBlank(token)) {
String message = "token is blank, get user failed";
log.warn("{}, uri: {}", message, request.getServletPath());
throw new AccessException(message);
}
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));
Integer isAdmin = claims.get(TOKEN_IS_ADMIN) == null
? 0 : Integer.parseInt(claims.get(TOKEN_IS_ADMIN).toString());
return UserWithPassword.get(userId, userName, displayName, email, password, isAdmin);
}
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

@@ -5,11 +5,9 @@
<result column="id" jdbcType="BIGINT" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="salt" jdbcType="VARCHAR" property="salt" />
<result column="display_name" jdbcType="VARCHAR" property="displayName" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="is_admin" jdbcType="INTEGER" property="isAdmin" />
<result column="last_login" jdbcType="TIMESTAMP" property="lastLogin" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@@ -41,7 +39,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, name, password, salt, display_name, email, is_admin, last_login
id, name, password, display_name, email, is_admin
</sql>
<select id="selectByExample" parameterType="com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDOExample" resultMap="BaseResultMap">
select
@@ -60,6 +58,14 @@
limit #{limitStart} , #{limitEnd}
</if>
</select>
<insert id="insert" parameterType="com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO">
insert into s2_user (id, name, password,
display_name, email, is_admin
)
values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},
#{displayName,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{isAdmin,jdbcType=INTEGER}
)
</insert>
<insert id="insertSelective" parameterType="com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO">
insert into s2_user
<trim prefix="(" suffix=")" suffixOverrides=",">
@@ -72,9 +78,6 @@
<if test="password != null">
password,
</if>
<if test="password != null">
salt,
</if>
<if test="displayName != null">
display_name,
</if>
@@ -95,9 +98,6 @@
<if test="password != null">
#{password,jdbcType=VARCHAR},
</if>
<if test="salt != null">
#{salt,jdbcType=VARCHAR},
</if>
<if test="displayName != null">
#{displayName,jdbcType=VARCHAR},
</if>
@@ -115,32 +115,4 @@
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByPrimaryKey" parameterType="com.tencent.supersonic.auth.authentication.persistence.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="salt != null">
salt = #{salt,jdbcType=VARCHAR},
</if>
<if test="displayName != null">
display_name = #{displayName,jdbcType=VARCHAR},
</if>
<if test="email != null">
email = #{email,jdbcType=VARCHAR},
</if>
<if test="isAdmin != null">
is_admin = #{isAdmin,jdbcType=INTEGER},
</if>
<if test="lastLogin != null">
last_login = #{lastLogin,jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>

View File

@@ -1,14 +0,0 @@
<?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.persistence.mapper.UserTokenDOMapper">
<resultMap id="BaseResultMap" type="com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO">
<result column="id" jdbcType="BIGINT" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="token" jdbcType="VARCHAR" property="token" />
<result column="expire_time" jdbcType="TIMESTAMP" property="expireTime" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
</resultMap>
</mapper>

View File

@@ -12,8 +12,8 @@
<artifactId>auth-authorization</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>

View File

@@ -1,13 +1,14 @@
package com.tencent.supersonic.auth.authorization.rest;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup;
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 com.tencent.supersonic.common.pojo.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup;
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;
@@ -16,8 +17,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/auth")
@Slf4j
@@ -35,7 +34,9 @@ public class AuthController {
return authService.queryAuthGroups(modelId, groupId);
}
/** 新建权限组 */
/**
* 新建权限组
*/
@PostMapping("/createGroup")
public void newAuthGroup(@RequestBody AuthGroup group) {
group.setGroupId(null);
@@ -68,7 +69,8 @@ public class AuthController {
*/
@PostMapping("/queryAuthorizedRes")
public AuthorizedResourceResp queryAuthorizedResources(@RequestBody QueryAuthResReq req,
HttpServletRequest request, HttpServletResponse response) {
HttpServletRequest request,
HttpServletResponse response) {
User user = UserHolder.findUser(request, response);
return authService.queryAuthorizedResources(req, user);
}

View File

@@ -1,25 +1,27 @@
package com.tencent.supersonic.auth.authorization.service;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.service.UserService;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRule;
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.api.authorization.service.AuthService;
import com.tencent.supersonic.common.pojo.User;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Map;
import java.util.ArrayList;
import java.util.stream.Collectors;
@Service
@@ -30,17 +32,16 @@ public class AuthServiceImpl implements AuthService {
private UserService userService;
public AuthServiceImpl(JdbcTemplate jdbcTemplate, UserService userService) {
public AuthServiceImpl(JdbcTemplate jdbcTemplate,
UserService userService) {
this.jdbcTemplate = jdbcTemplate;
this.userService = userService;
}
private List<AuthGroup> load() {
List<String> rows =
jdbcTemplate.queryForList("select config from s2_auth_groups", String.class);
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());
return rows.stream().map(row -> g.fromJson(row, AuthGroup.class)).collect(Collectors.toList());
}
@Override
@@ -62,11 +63,11 @@ public class AuthServiceImpl implements AuthService {
nextGroupId = obj + 1;
}
group.setGroupId(nextGroupId);
jdbcTemplate.update("insert into s2_auth_groups (group_id, config) values (?, ?);",
nextGroupId, g.toJson(group));
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());
jdbcTemplate.update("update s2_auth_groups set config = ? where group_id = ?;", g.toJson(group),
group.getGroupId());
}
}
@@ -77,60 +78,77 @@ public class AuthServiceImpl implements AuthService {
@Override
public AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, User user) {
if (CollectionUtils.isEmpty(req.getModelIds())) {
return new AuthorizedResourceResp();
}
Set<String> userOrgIds = userService.getUserAllOrgId(user.getName());
List<AuthGroup> groups =
getAuthGroups(req.getModelIds(), user.getName(), new ArrayList<>(userOrgIds));
List<AuthGroup> groups = getAuthGroups(req.getModelIds(), user.getName(), new ArrayList<>(userOrgIds));
AuthorizedResourceResp resource = new AuthorizedResourceResp();
Map<Long, List<AuthGroup>> authGroupsByModelId =
groups.stream().collect(Collectors.groupingBy(AuthGroup::getModelId));
for (Long modelId : req.getModelIds()) {
Map<Long, List<AuthGroup>> authGroupsByModelId = groups.stream()
.collect(Collectors.groupingBy(AuthGroup::getModelId));
Map<Long, List<AuthRes>> reqAuthRes = req.getResources().stream()
.collect(Collectors.groupingBy(AuthRes::getModelId));
for (Long modelId : reqAuthRes.keySet()) {
List<AuthRes> reqResourcesList = reqAuthRes.get(modelId);
AuthResGrp rg = new AuthResGrp();
if (authGroupsByModelId.containsKey(modelId)) {
List<AuthGroup> authGroups = authGroupsByModelId.get(modelId);
for (AuthGroup authRuleGroup : authGroups) {
List<AuthRule> authRules = authRuleGroup.getAuthRules();
for (AuthRule authRule : authRules) {
for (String resBizName : authRule.resourceNames()) {
resource.getAuthResList().add(new AuthRes(modelId, resBizName));
for (AuthRes reqRes : reqResourcesList) {
for (AuthGroup authRuleGroup : authGroups) {
List<AuthRule> authRules = authRuleGroup.getAuthRules();
List<String> allAuthItems = new ArrayList<>();
authRules.forEach(authRule -> allAuthItems.addAll(authRule.resourceNames()));
if (allAuthItems.contains(reqRes.getName())) {
rg.getGroup().add(reqRes);
}
}
}
}
if (!CollectionUtils.isEmpty(rg.getGroup())) {
resource.getResources().add(rg);
}
}
Set<Map.Entry<Long, List<AuthGroup>>> entries = authGroupsByModelId.entrySet();
for (Map.Entry<Long, List<AuthGroup>> entry : entries) {
List<AuthGroup> authGroups = entry.getValue();
for (AuthGroup authGroup : authGroups) {
DimensionFilter df = new DimensionFilter();
df.setDescription(authGroup.getDimensionFilterDescription());
df.setExpressions(authGroup.getDimensionFilters());
resource.getFilters().add(df);
if (!CollectionUtils.isEmpty(req.getModelIds())) {
List<AuthGroup> authGroups = Lists.newArrayList();
for (Long modelId : authGroupsByModelId.keySet()) {
authGroups.addAll(authGroupsByModelId.getOrDefault(modelId, Lists.newArrayList()));
}
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;
}
private List<AuthGroup> getAuthGroups(List<Long> modelIds, String userName,
List<String> departmentIds) {
List<AuthGroup> groups = load().stream().filter(group -> {
if (!modelIds.contains(group.getModelId())) {
return false;
}
if (!CollectionUtils.isEmpty(group.getAuthorizedUsers())
&& group.getAuthorizedUsers().contains(userName)) {
return true;
}
for (String departmentId : departmentIds) {
if (!CollectionUtils.isEmpty(group.getAuthorizedDepartmentIds())
&& group.getAuthorizedDepartmentIds().contains(departmentId)) {
return true;
}
}
return false;
}).collect(Collectors.toList());
private List<AuthGroup> getAuthGroups(List<Long> modelIds, String userName, List<String> departmentIds) {
List<AuthGroup> groups = load().stream()
.filter(group -> {
if (CollectionUtils.isEmpty(modelIds) || !modelIds.contains(group.getModelId())) {
return false;
}
if (!CollectionUtils.isEmpty(group.getAuthorizedUsers()) && group.getAuthorizedUsers()
.contains(userName)) {
return true;
}
for (String departmentId : departmentIds) {
if (!CollectionUtils.isEmpty(group.getAuthorizedDepartmentIds())
&& group.getAuthorizedDepartmentIds().contains(departmentId)) {
return true;
}
}
return false;
}).collect(Collectors.toList());
log.info("user:{} department:{} authGroups:{}", userName, departmentIds, groups);
return groups;
}
}

View File

@@ -1,84 +0,0 @@
## 使用场景
产品上线阶段批量测试问答对话的问题,统计测试结果。
注意与evaluation模块的区别evaluation是构建数据集多个模型的横向评估benchmark是选定模型下批量自动化业务问题的测试。
## 功能说明
批量自动化测试问答对话测试,支持单轮问答测试。
## 使用说明
- 注意1建议在开发测试环境的执行如果需要在生产环境的测试请避开用户使用高峰期。
- 注意2: python版本要求3.8可以运行。3.8.5版本测试通过。以上python版本未测试。
1. 准备测试问题
将问题写入`test_data.csv`文件,格式如下:
```csv
question
各BG期间在职、入职、离职人员的平均薪资是多少注意薪资不包括香港视源、广视以及并购控股子公司青松、仙视的数据。
各BG期间入职且仍在职的人数有多少
各BG当月的净增长人数及其增长率是多少
```
将文件放入`benchmark/data`目录下。
2. 执行测试
```bash
python benchmark -u http://localhost:3100 -a 6 -c 141 -f data/renli.csv -p zds
```
参数说明:
- -a: 问答对话的id
- -c: chat_id
- -f: 测试问题文件
- -u: 用户id
如果执行报错没有安装相关python包可以执行`pip install -r requirements.txt`安装相关包。
3. 查看测试结果
当前,只能在数据库中查看测试结果。
```sql
select question_id,chat_id,create_time,query_text,
JSON_EXTRACT(parse_info,'$.sqlInfo.s2SQL') as s2sql,
JSON_EXTRACT(parse_info,'$.sqlInfo.correctS2SQL') as correctS2SQL,
JSON_EXTRACT(parse_info,'$.sqlInfo.querySQL') as querySQL,
'请标记正确的SQL' as correctSQL,
'请标记生成SQL是否正确' as isOk,
'请分类不正确的原因' as reason
from s2_chat_parse scp where user_name = 'zhaodongsheng' and chat_id = '141';
select question_id,chat_id,create_time,query_text,
JSON_EXTRACT(query_result,'$.querySql') as querySql,
JSON_EXTRACT(query_result,'$.queryResults') as queryResults
from s2_chat_query where user_name = 'zhaodongsheng' and chat_id = '141' and query_state = 1;
```
4. 查看帮助
```bash
python benchmark.py --help
usage: benchmark.py [-h] -u URL -a AGENTID -c CHATID -f FILEPATH -p USERNAME
optional arguments:
-h, --help show this help message and exit
-u URL, --url URL url:问答系统url,例如https://chatdata-dev.test.com
-a AGENTID, --agentId AGENTID
agentId助手ID
-c CHATID, --chatId CHATID
chatId:会话ID,需要通过浏览器开发者模式获取
-f FILEPATH, --filePath FILEPATH
filePath问题文件路径, csv格式. 请提前上传到benchmark/data目录下
-p USERNAME, --userName USERNAME
userName用户名用户获取登录token
```
## 演示效果
```bash
python benchmark.py -u https://chatdata-dev.test.com -a 3 -c 35 -f data/shuce.csv -p zds
批量测试配置信息[url: https://chatdata-dev.test.com agentId: 3 chatId: 35 filePath: data/shuce.csv userName: zds ]
请确认输入的压力测试信息是否正确:
1. Yes
2. No
1
start to ask question: 各BG期间在职、入职、离职人员的平均薪资是多少注意薪资不包括香港视源、广视以及并购控股子公司青松、仙视的数据。
start to ask question: 各BG期间入职且仍在职的人数有多少
start to ask question: 各BG当月的净增长人数及其增长率是多少
```
## TODO
- [x] 问答对话测试
- [ ] 多轮对话测试
- [ ] 问答对话测试结果展示

View File

@@ -1,184 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# -----------------------------------------------------------------------------------
'''
@filename : batchmark.py
@time : 2024/06/20
@author : zhaodongsheng
@Version : 1.0
@description : 批量问答测试
'''
# -----------------------------------------------------------------------------------
import pandas as pd
import json
import requests
import time
import jwt
import traceback
import os
from datetime import datetime
class DataFrameAppender:
def __init__(self,file_name = "output"):
# 定义表头
columns = ['问题', '解析状态', '解析耗时', '执行状态', '执行耗时', '总耗时']
# 创建只有表头的 DataFrame
self.df = pd.DataFrame(columns=columns)
self.file_name = file_name
def append_data(self, new_data):
# 假设 new_data 是一维数组,将其转换为字典
columns = ['问题', '解析状态', '解析耗时', '执行状态', '执行耗时', '总耗时']
new_dict = dict(zip(columns, new_data))
# 使用 loc 方法追加数据
self.df.loc[len(self.df)] = new_dict
def print_analysis_result(self):
# 测试样例总数
total_samples = len(self.df)
# 解析成功数量
parse_success_count = (self.df['解析状态'] == '解析成功').sum()
# 执行成功数量
execute_success_count = (self.df['执行状态'] == '执行成功').sum()
# 解析平均耗时,保留两位小数
avg_parse_time = round(self.df['解析耗时'].mean(), 2)
# 执行平均耗时,保留两位小数
avg_execute_time = round(self.df['执行耗时'].mean(), 2)
# 总平均耗时,保留两位小数
avg_total_time = round(self.df['总耗时'].mean(), 2)
# 最长耗时,保留两位小数
max_time = round(self.df['总耗时'].max(), 2)
# 最短耗时,保留两位小数
min_time = round(self.df['总耗时'].min(), 2)
print(f"测试样例总数 : {total_samples}")
print(f"解析成功数量 : {parse_success_count}")
print(f"执行成功数量 : {execute_success_count}")
print(f"解析平均耗时 : {avg_parse_time}")
print(f"执行平均耗时 : {avg_execute_time}")
print(f"总平均耗时 : {avg_total_time}")
print(f"最长耗时 : {max_time}")
print(f"最短耗时 : {min_time}")
def write_to_csv(self):
# 检查 data 文件夹是否存在,如果不存在则创建
if not os.path.exists('res'):
os.makedirs('res')
# 获取当前时间戳
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
# 生成带时间戳的文件名
file_path = os.path.join('res', f'{self.file_name}_{timestamp}.csv')
self.df.to_csv(file_path, index=False)
print(f"测试结果已保存到 {file_path}")
class BatchTest:
def __init__(self, url, agentId, chatId, userName):
self.base_url = url + '/api/chat/query/'
self.agentId = agentId
self.auth_token = self.__get_authorization(userName)
self.chatId = chatId
def parse(self, query_text):
url = self.base_url + 'parse'
data = {
'queryText': query_text,
'agentId': self.agentId,
'chatId': self.chatId,
}
headers = {
'Authorization': 'Bearer ' + self.auth_token,
'Content-Type': 'application/json',
}
response = requests.post(url, headers=headers, data=json.dumps(data))
return response.json()
def execute(self, agentId, query_text, queryId):
url = self.base_url + 'execute'
data = {
'agentId': agentId,
'queryText': query_text,
'parseId': 1,
'chatId': self.chatId,
'queryId': queryId,
}
headers = {
'Authorization': 'Bearer ' + self.auth_token,
'Content-Type': 'application/json',
}
response = requests.post(url, headers=headers, data=json.dumps(data))
return response.json()
def read_question_from_csv(self, filePath):
df = pd.read_csv(filePath)
return df
def __get_authorization(self, userName):
# secret 请和 com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig.tokenAppSecret 保持一致
secret = "WIaO9YRRVt+7QtpPvyWsARFngnEcbaKBk783uGFwMrbJBaochsqCH62L4Kijcb0sZCYoSsiKGV/zPml5MnZ3uQ=="
exp = time.time() + 100000000
token= jwt.encode({"token_user_name": userName,"exp": exp}, secret, algorithm="HS512")
return token
def benchmark(url:str, agentId:str, chatId:str, filePath:str, userName:str):
batch_test = BatchTest(url, agentId, chatId, userName)
df = batch_test.read_question_from_csv(filePath)
appender = DataFrameAppender(os.path.basename(filePath))
for index, row in df.iterrows():
question = row['question']
print('start to ask question:', question)
# 捕获异常,防止程序中断
try:
parse_resp = batch_test.parse(question)
parse_status = '解析失败'
if parse_resp.get('data').get('errorMsg') is None:
parse_status = '解析成功'
parse_cost = parse_resp.get('data').get('parseTimeCost').get('parseTime')
execute_resp = batch_test.execute(agentId, question, parse_resp['data']['queryId'])
execute_status = '执行失败'
execute_cost = 0
if parse_status == '解析成功' and execute_resp.get('data').get('errorMsg') is None:
execute_status = '执行成功'
execute_cost = execute_resp.get('data').get('queryTimeCost')
res = [question.replace(',', '#'),parse_status,parse_cost/1000,execute_status,execute_cost/1000,(parse_cost+execute_cost)/1000]
appender.append_data(res)
except Exception as e:
print('error:', e)
traceback.print_exc()
continue
time.sleep(1)
# 打印分析结果
appender.print_analysis_result()
# 分析明细输出
appender.write_to_csv()
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--url', type=str, required=True, help='url:问答系统url,例如https://chatdata-dev.test.com')
parser.add_argument('-a', '--agentId', type=str, required=True, help='agentId助手ID')
parser.add_argument('-c', '--chatId', type=str, required=True, help='chatId:会话ID,需要通过浏览器开发者模式获取')
parser.add_argument('-f', '--filePath', type=str, required=True, help='filePath问题文件路径, csv格式. 请提前上传到benchmark/data目录下')
parser.add_argument('-p', '--userName', type=str, required=True, help='userName用户名用户获取登录token')
args = parser.parse_args()
print('批量测试配置信息[url:', args.url,'agentId:', args.agentId, 'chatId:', args.chatId, 'filePath:', args.filePath, 'userName:', args.userName, ']')
print('请确认输入的压力测试信息是否正确:')
print('1. Yes')
print('2. No')
confirm = input()
if confirm == '1' or confirm == 'Yes' or confirm == 'yes' or confirm == 'YES':
benchmark(args.url, args.agentId, args.chatId, args.filePath, args.userName)
else:
print('请重新输入压力测试配置信息: url, agentId, chatId, filePath, userName')

View File

@@ -1,3 +0,0 @@
question
每个业务组BG的员工人数是多少
每个业务组的损益情况如何?
1 question
2 每个业务组(BG)的员工人数是多少?
3 每个业务组的损益情况如何?

View File

@@ -1,8 +0,0 @@
question
在职人员的男女比例是多少?
期间入职且离职的人数及其占比如何?
期间新入职社招人员的平均年龄是多少?
期间入职且在职的人数有多少?
期间在职人员的平均年龄是多少?
当月的净增长人数及其增长率是多少?
期间新入职社招人员的年龄分布情况如何?
1 question
2 在职人员的男女比例是多少?
3 期间入职且离职的人数及其占比如何?
4 期间新入职社招人员的平均年龄是多少?
5 期间入职且在职的人数有多少?
6 期间在职人员的平均年龄是多少?
7 当月的净增长人数及其增长率是多少?
8 期间新入职社招人员的年龄分布情况如何?

View File

@@ -1,4 +0,0 @@
question
在广东省内,哪一个学校的累计集备数最多,请返回该学校的学校名称
在广东省内,哪一个学校的累计集体备课数最多,请返回该学校的学校名称
1 question
2 在广东省内,哪一个学校的累计集备数最多,请返回该学校的学校名称
3 在广东省内,哪一个学校的累计集体备课数最多,请返回该学校的学校名称

View File

@@ -1,5 +0,0 @@
pandas==2.2.2
PyJWT==2.8.0
requests==2.28.2

View File

@@ -0,0 +1,24 @@
package com.tencent.supersonic.chat.api.pojo;
import com.tencent.supersonic.headless.api.pojo.SchemaElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SchemaElementMatch {
SchemaElement element;
double similarity;
String detectWord;
String word;
Long frequency;
boolean isInherited;
}

View File

@@ -0,0 +1,33 @@
package com.tencent.supersonic.chat.api.pojo;
import com.google.common.collect.Lists;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SchemaMapInfo {
private Map<Long, List<SchemaElementMatch>> viewElementMatches = new HashMap<>();
public Set<Long> getMatchedViewInfos() {
return viewElementMatches.keySet();
}
public List<SchemaElementMatch> getMatchedElements(Long view) {
return viewElementMatches.getOrDefault(view, Lists.newArrayList());
}
public Map<Long, List<SchemaElementMatch>> getViewElementMatches() {
return viewElementMatches;
}
public void setViewElementMatches(Map<Long, List<SchemaElementMatch>> viewElementMatches) {
this.viewElementMatches = viewElementMatches;
}
public void setMatchedElements(Long view, List<SchemaElementMatch> elementMatches) {
viewElementMatches.put(view, elementMatches);
}
}

View File

@@ -1,6 +1,6 @@
package com.tencent.supersonic.headless.api.pojo;
package com.tencent.supersonic.chat.api.pojo;
import com.tencent.supersonic.headless.api.pojo.request.QueryFilters;
import com.tencent.supersonic.chat.api.pojo.request.QueryFilters;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

View File

@@ -0,0 +1,86 @@
package com.tencent.supersonic.chat.api.pojo;
import com.tencent.supersonic.chat.api.pojo.response.SqlInfo;
import com.tencent.supersonic.chat.api.pojo.request.QueryFilter;
import com.tencent.supersonic.chat.api.pojo.response.EntityInfo;
import com.tencent.supersonic.common.pojo.DateConf;
import com.tencent.supersonic.common.pojo.Order;
import com.tencent.supersonic.common.pojo.enums.QueryType;
import com.tencent.supersonic.common.pojo.enums.AggregateTypeEnum;
import com.tencent.supersonic.common.pojo.enums.FilterType;
import com.tencent.supersonic.headless.api.pojo.SchemaElement;
import lombok.Data;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@Data
public class SemanticParseInfo {
private Integer id;
private String queryMode;
private SchemaElement view;
private Set<SchemaElement> metrics = new TreeSet<>(new SchemaNameLengthComparator());
private Set<SchemaElement> dimensions = new LinkedHashSet();
private SchemaElement entity;
private AggregateTypeEnum aggType = AggregateTypeEnum.NONE;
private FilterType filterType = FilterType.UNION;
private Set<QueryFilter> dimensionFilters = new LinkedHashSet();
private Set<QueryFilter> metricFilters = new LinkedHashSet();
private Set<Order> orders = new LinkedHashSet();
private DateConf dateInfo;
private Long limit;
private double score;
private List<SchemaElementMatch> elementMatches = new ArrayList<>();
private Map<String, Object> properties = new HashMap<>();
private EntityInfo entityInfo;
private SqlInfo sqlInfo = new SqlInfo();
private QueryType queryType = QueryType.ID;
private static class SchemaNameLengthComparator implements Comparator<SchemaElement> {
@Override
public int compare(SchemaElement o1, SchemaElement o2) {
if (o1.getOrder() != o2.getOrder()) {
if (o1.getOrder() < o2.getOrder()) {
return -1;
} else {
return 1;
}
}
int len1 = o1.getName().length();
int len2 = o2.getName().length();
if (len1 != len2) {
return len1 - len2;
} else {
return o1.getName().compareTo(o2.getName());
}
}
}
public Set<SchemaElement> getMetrics() {
Set<SchemaElement> metricSet = new TreeSet<>(new SchemaNameLengthComparator());
metricSet.addAll(metrics);
metrics = metricSet;
return metrics;
}
public Long getViewId() {
if (view == null) {
return null;
}
return view.getView();
}
public SchemaElement getModel() {
return view;
}
}

View File

@@ -0,0 +1,157 @@
package com.tencent.supersonic.chat.api.pojo;
import com.tencent.supersonic.headless.api.pojo.SchemaElement;
import com.tencent.supersonic.headless.api.pojo.SchemaElementType;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.util.CollectionUtils;
public class SemanticSchema implements Serializable {
private List<ViewSchema> viewSchemaList;
public SemanticSchema(List<ViewSchema> viewSchemaList) {
this.viewSchemaList = viewSchemaList;
}
public void add(ViewSchema schema) {
viewSchemaList.add(schema);
}
public SchemaElement getElement(SchemaElementType elementType, long elementID) {
Optional<SchemaElement> element = Optional.empty();
switch (elementType) {
case ENTITY:
element = getElementsById(elementID, getEntities());
break;
case VIEW:
element = getElementsById(elementID, getViews());
break;
case METRIC:
element = getElementsById(elementID, getMetrics());
break;
case DIMENSION:
element = getElementsById(elementID, getDimensions());
break;
case VALUE:
element = getElementsById(elementID, getDimensionValues());
break;
default:
}
if (element.isPresent()) {
return element.get();
} else {
return null;
}
}
public Map<Long, String> getViewIdToName() {
return viewSchemaList.stream()
.collect(Collectors.toMap(a -> a.getView().getId(), a -> a.getView().getName(), (k1, k2) -> k1));
}
public List<SchemaElement> getDimensionValues() {
List<SchemaElement> dimensionValues = new ArrayList<>();
viewSchemaList.stream().forEach(d -> dimensionValues.addAll(d.getDimensionValues()));
return dimensionValues;
}
public List<SchemaElement> getDimensions() {
List<SchemaElement> dimensions = new ArrayList<>();
viewSchemaList.stream().forEach(d -> dimensions.addAll(d.getDimensions()));
return dimensions;
}
public List<SchemaElement> getDimensions(Long viewId) {
List<SchemaElement> dimensions = getDimensions();
return getElementsByViewId(viewId, dimensions);
}
public SchemaElement getDimension(Long id) {
List<SchemaElement> dimensions = getDimensions();
Optional<SchemaElement> dimension = getElementsById(id, dimensions);
return dimension.orElse(null);
}
public List<SchemaElement> getTags() {
List<SchemaElement> tags = new ArrayList<>();
viewSchemaList.stream().forEach(d -> tags.addAll(d.getTags()));
return tags;
}
public List<SchemaElement> getTags(Long viewId) {
List<SchemaElement> tags = new ArrayList<>();
viewSchemaList.stream().filter(schemaElement ->
viewId.equals(schemaElement.getView().getView()))
.forEach(d -> tags.addAll(d.getTags()));
return tags;
}
public List<SchemaElement> getMetrics() {
List<SchemaElement> metrics = new ArrayList<>();
viewSchemaList.stream().forEach(d -> metrics.addAll(d.getMetrics()));
return metrics;
}
public List<SchemaElement> getMetrics(Long viewId) {
List<SchemaElement> metrics = getMetrics();
return getElementsByViewId(viewId, metrics);
}
public List<SchemaElement> getEntities() {
List<SchemaElement> entities = new ArrayList<>();
viewSchemaList.stream().forEach(d -> entities.add(d.getEntity()));
return entities;
}
public List<SchemaElement> getEntities(Long viewId) {
List<SchemaElement> entities = getEntities();
return getElementsByViewId(viewId, entities);
}
private List<SchemaElement> getElementsByViewId(Long viewId, List<SchemaElement> elements) {
return elements.stream()
.filter(schemaElement -> viewId.equals(schemaElement.getView()))
.collect(Collectors.toList());
}
private Optional<SchemaElement> getElementsById(Long id, List<SchemaElement> elements) {
return elements.stream()
.filter(schemaElement -> id.equals(schemaElement.getId()))
.findFirst();
}
public SchemaElement getView(Long viewId) {
List<SchemaElement> views = getViews();
return getElementsById(viewId, views).orElse(null);
}
public List<SchemaElement> getViews() {
List<SchemaElement> views = new ArrayList<>();
viewSchemaList.stream().forEach(d -> views.add(d.getView()));
return views;
}
public Map<String, String> getBizNameToName(Long viewId) {
List<SchemaElement> allElements = new ArrayList<>();
allElements.addAll(getDimensions(viewId));
allElements.addAll(getMetrics(viewId));
return allElements.stream()
.collect(Collectors.toMap(SchemaElement::getBizName, SchemaElement::getName, (k1, k2) -> k1));
}
public Map<Long, ViewSchema> getViewSchemaMap() {
if (CollectionUtils.isEmpty(viewSchemaList)) {
return new HashMap<>();
}
return viewSchemaList.stream().collect(Collectors.toMap(viewSchema
-> viewSchema.getView().getView(), viewSchema -> viewSchema));
}
}

View File

@@ -0,0 +1,113 @@
package com.tencent.supersonic.chat.api.pojo;
import com.tencent.supersonic.headless.api.pojo.QueryConfig;
import com.tencent.supersonic.headless.api.pojo.SchemaElement;
import com.tencent.supersonic.headless.api.pojo.SchemaElementType;
import com.tencent.supersonic.headless.api.pojo.TagTypeDefaultConfig;
import com.tencent.supersonic.headless.api.pojo.TimeDefaultConfig;
import lombok.Data;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@Data
public class ViewSchema {
private SchemaElement view;
private Set<SchemaElement> metrics = new HashSet<>();
private Set<SchemaElement> dimensions = new HashSet<>();
private Set<SchemaElement> dimensionValues = new HashSet<>();
private Set<SchemaElement> tags = new HashSet<>();
private SchemaElement entity = new SchemaElement();
private QueryConfig queryConfig;
public SchemaElement getElement(SchemaElementType elementType, long elementID) {
Optional<SchemaElement> element = Optional.empty();
switch (elementType) {
case ENTITY:
element = Optional.ofNullable(entity);
break;
case VIEW:
element = Optional.of(view);
break;
case METRIC:
element = metrics.stream().filter(e -> e.getId() == elementID).findFirst();
break;
case DIMENSION:
element = dimensions.stream().filter(e -> e.getId() == elementID).findFirst();
break;
case VALUE:
element = dimensionValues.stream().filter(e -> e.getId() == elementID).findFirst();
break;
case TAG:
element = tags.stream().filter(e -> e.getId() == elementID).findFirst();
break;
default:
}
if (element.isPresent()) {
return element.get();
} else {
return null;
}
}
public SchemaElement getElement(SchemaElementType elementType, String name) {
Optional<SchemaElement> element = Optional.empty();
switch (elementType) {
case ENTITY:
element = Optional.ofNullable(entity);
break;
case VIEW:
element = Optional.of(view);
break;
case METRIC:
element = metrics.stream().filter(e -> name.equals(e.getName())).findFirst();
break;
case DIMENSION:
element = dimensions.stream().filter(e -> name.equals(e.getName())).findFirst();
break;
case VALUE:
element = dimensionValues.stream().filter(e -> name.equals(e.getName())).findFirst();
break;
default:
}
if (element.isPresent()) {
return element.get();
} else {
return null;
}
}
public TimeDefaultConfig getTagTypeTimeDefaultConfig() {
if (queryConfig == null) {
return null;
}
if (queryConfig.getTagTypeDefaultConfig() == null) {
return null;
}
return queryConfig.getTagTypeDefaultConfig().getTimeDefaultConfig();
}
public TimeDefaultConfig getMetricTypeTimeDefaultConfig() {
if (queryConfig == null) {
return null;
}
if (queryConfig.getMetricTypeDefaultConfig() == null) {
return null;
}
return queryConfig.getMetricTypeDefaultConfig().getTimeDefaultConfig();
}
public TagTypeDefaultConfig getTagTypeDefaultConfig() {
if (queryConfig == null) {
return null;
}
return queryConfig.getTagTypeDefaultConfig();
}
}

View File

@@ -1,18 +0,0 @@
package com.tencent.supersonic.chat.api.pojo.enums;
import com.tencent.supersonic.common.pojo.exception.InvalidArgumentException;
import org.apache.commons.lang3.StringUtils;
public enum MemoryReviewResult {
POSITIVE, NEGATIVE;
public static MemoryReviewResult getMemoryReviewResult(String value) {
String validValue = StringUtils.trim(value);
for (MemoryReviewResult reviewRet : MemoryReviewResult.values()) {
if (StringUtils.equalsIgnoreCase(reviewRet.name(), validValue)) {
return reviewRet;
}
}
throw new InvalidArgumentException("Invalid MemoryReviewResult type:[" + value + "]");
}
}

View File

@@ -1,5 +0,0 @@
package com.tencent.supersonic.chat.api.pojo.enums;
public enum MemoryStatus {
PENDING, ENABLED, DISABLED;
}

View File

@@ -7,13 +7,18 @@ import java.util.List;
@Data
public class ChatAggConfigReq {
/** invisible dimensions/metrics */
/**
* invisible dimensions/metrics
*/
private ItemVisibility visibility;
/** information about dictionary about the model */
/**
* information about dictionary about the model
*/
private List<KnowledgeInfoReq> knowledgeInfos;
private KnowledgeAdvancedConfig globalKnowledgeConfig;
private ChatDefaultConfigReq chatDefaultConfig;
}

View File

@@ -1,23 +1,45 @@
package com.tencent.supersonic.chat.api.pojo.request;
import com.tencent.supersonic.common.pojo.enums.StatusEnum;
import lombok.Data;
import lombok.ToString;
import java.util.List;
/** extended information command about model */
/**
* extended information command about model
*/
@Data
@ToString
public class ChatConfigBaseReq {
private Long modelId;
/** the recommended questions about the model */
/**
* the chatDetailConfig about the model
*/
private ChatDetailConfigReq chatDetailConfig;
/**
* the chatAggConfig about the model
*/
private ChatAggConfigReq chatAggConfig;
/**
* the recommended questions about the model
*/
private List<RecommendedQuestionReq> recommendedQuestions;
/** the llm examples about the model */
/**
* the llm examples about the model
*/
private String llmExamples;
/** available status */
/**
* available status
*/
private StatusEnum status;
}

View File

@@ -2,6 +2,7 @@ package com.tencent.supersonic.chat.api.pojo.request;
import lombok.Data;
@Data
public class ChatConfigEditReqReq extends ChatConfigBaseReq {

View File

@@ -4,6 +4,7 @@ import com.tencent.supersonic.common.pojo.enums.StatusEnum;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Data
public class ChatConfigFilter {

View File

@@ -1,5 +1,6 @@
package com.tencent.supersonic.chat.api.pojo.request;
import lombok.Data;
import java.util.ArrayList;
@@ -10,4 +11,6 @@ public class ChatDefaultConfigReq {
private List<Long> dimensionIds = new ArrayList<>();
private List<Long> metricIds = new ArrayList<>();
}

View File

@@ -7,13 +7,18 @@ import java.util.List;
@Data
public class ChatDetailConfigReq {
/** invisible dimensions/metrics */
/**
* invisible dimensions/metrics
*/
private ItemVisibility visibility;
/** information about dictionary about the model */
/**
* information about dictionary about the model
*/
private List<KnowledgeInfoReq> knowledgeInfos;
private KnowledgeAdvancedConfig globalKnowledgeConfig;
private ChatDefaultConfigReq chatDefaultConfig;
}

Some files were not shown because too many files have changed in this diff Show More