diff --git a/CHANGELOG.md b/CHANGELOG.md index e0cb1a3cb..186c25381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 3.1.19 + +`2024-1-3` + +**Changelog** + +- ⭐【New Features】Open table supports filtering sort +- ⚡️【Optimize】Optimize startup speed +- 🐞【Fixed】SQL error generated by modifying the table structure +- 🐞【Fixed】The problem of direct query table data error due to characters +- 🐞【Fixed】null point error + + ## 3.1.18 `2023-12-28` diff --git a/CHANGELOG_CN.md b/CHANGELOG_CN.md index d52aa8b0c..13b54237c 100644 --- a/CHANGELOG_CN.md +++ b/CHANGELOG_CN.md @@ -1,3 +1,15 @@ +## 3.1.19 + +`2024-1-3` + +**更新日志** + +- ⭐【新功能】打开表支持筛选排序 +- ⚡️【优化】优化启动速度 +- 🐞【修复】修改表结构生成SQL错误 +- 🐞【修复】直接查询表因字符导致数据错误的问题 +- 🐞【修复】null point error + ## 3.1.18 `2023-12-28` diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 5d91bd647..fd84059df 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -1,13 +1,13 @@ import React, { useEffect, useState } from 'react'; import configService from '@/service/config'; import { AIType } from '@/typings/ai'; -import { Alert, Button, Form, Input, Radio, RadioChangeEvent, Spin } from 'antd'; +import { Alert, Button, Form, Input, Radio, RadioChangeEvent } from 'antd'; import i18n from '@/i18n'; import { IAiConfig } from '@/typings/setting'; -import { getUser } from '@/service/user'; -import { ILoginUser, IRole } from '@/typings/user'; +import { IRole } from '@/typings/user'; import { AIFormConfig, AITypeName } from './aiTypeConfig'; import styles from './index.less'; +import { useUserStore } from '@/store/user' interface IProps { handleApplyAiConfig: (aiConfig: IAiConfig) => void; @@ -21,34 +21,16 @@ function capitalizeFirstLetter(string) { // openAI 的设置项 export default function SettingAI(props: IProps) { const [aiConfig, setAiConfig] = useState(); - const [userInfo, setUserInfo] = useState(); - const [loading, setLoading] = useState(false); - - const queryUserInfo = async () => { - setLoading(true); - try { - const res = await getUser(); - // 向cookie中写入当前用户id - const date = new Date('2030-12-30 12:30:00').toUTCString(); - document.cookie = `CHAT2DB.USER_ID=${res?.id};Expires=${date}`; - setUserInfo(res); - } finally { - setLoading(false); + const { userInfo } = useUserStore(state => { + return { + userInfo: state.curUser } - }; - - useEffect(() => { - queryUserInfo(); - }, []); + }) useEffect(() => { setAiConfig(props.aiConfig); }, [props.aiConfig]); - if (loading) { - return ; - } - if (!aiConfig) { return ; } @@ -84,7 +66,7 @@ export default function SettingAI(props: IProps) { }; return ( - + <>
{i18n('setting.title.aiSource')}:
@@ -128,6 +110,6 @@ export default function SettingAI(props: IProps) {
{/* {aiConfig?.aiSqlSource === AIType.CHAT2DBAI && !aiConfig.apiKey && } */} -
+ ); } diff --git a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx index e277204e9..e4318a2a9 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx @@ -15,6 +15,10 @@ import { registerIntelliSenseKeyword, registerIntelliSenseTable, registerIntelliSenseDatabase, + resetSenseKeyword, + resetSenseTable, + resetSenseDatabase, + resetSenseField, } from '@/utils/IntelliSense'; interface IProps { @@ -42,6 +46,15 @@ const SelectBoundInfo = memo((props: IProps) => { const [schemaList, setSchemaList] = useState[]>([emptyOption]); const [allTableList, setAllTableList] = useState([]); + useEffect(() => { + if(!isActive){ + resetSenseKeyword(); + resetSenseTable(); + resetSenseDatabase(); + resetSenseField(); + } + }, [isActive]); + const dataSourceList = useMemo(() => { return ( connectionList?.map((item) => ({ @@ -55,16 +68,19 @@ const SelectBoundInfo = memo((props: IProps) => { const supportDatabase = useMemo(() => { return connectionList?.find((item) => item.id === boundInfo.dataSourceId)?.supportDatabase; - }, [boundInfo.dataSourceId,connectionList]); + }, [boundInfo.dataSourceId, connectionList]); const supportSchema = useMemo(() => { return connectionList?.find((item) => item.id === boundInfo.dataSourceId)?.supportSchema; - }, [boundInfo.dataSourceId,connectionList]); + }, [boundInfo.dataSourceId, connectionList]); // 编辑器绑定的数据库类型变化时,重新注册智能提示 useEffect(() => { + if(!isActive){ + return + } registerIntelliSenseKeyword(boundInfo.databaseType); - }, [boundInfo.dataSourceId]); + }, [boundInfo.dataSourceId, isActive]); // 当数据源变化时,重新获取数据库列表 useEffect(() => { diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index afa70b7c2..f4c6c19c9 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -9,9 +9,9 @@ if (__ENV__ === 'local') { /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_oqofqe5r679.woff2?t=1703676557975') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_oqofqe5r679.woff?t=1703676557975') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_oqofqe5r679.ttf?t=1703676557975') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_yr9ay65j0fs.woff2?t=1703837870848') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_yr9ay65j0fs.woff?t=1703837870848') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_yr9ay65j0fs.ttf?t=1703837870848') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/components/MonacoEditor/index.tsx b/chat2db-client/src/components/MonacoEditor/index.tsx index 830d89b5b..f8cebfa45 100644 --- a/chat2db-client/src/components/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/MonacoEditor/index.tsx @@ -33,8 +33,8 @@ interface IProps { defaultValue?: string; appendValue?: IAppendValue; didMount?: (editor: IEditorIns) => any; - shortcutKey?: (editor, monaco) => void; - isActive?: boolean; + shortcutKey?: (editor, monaco, isActive: boolean) => void; + focusChange?: (isActive: boolean) => void; } export interface IExportRefFunction { @@ -51,7 +51,6 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { language = 'sql', didMount, options, - isActive, defaultValue, appendValue, shortcutKey, @@ -59,6 +58,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { const editorRef = useRef(); const quickInputCommand = useRef(); const [appTheme] = useTheme(); + const [isActive, setIsActive] = React.useState(false); // init useEffect(() => { @@ -108,12 +108,32 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }; }, []); + // 如果编辑器聚焦,就设置为true useEffect(() => { - if (editorRef.current && isActive) { + const focus = () => { + setIsActive(true); + props.focusChange && props.focusChange(true); + }; + const blur = () => { + setIsActive(false); + props.focusChange && props.focusChange(false); + }; + editorRef.current?.onDidFocusEditorText(focus); + editorRef.current?.onDidBlurEditorText(blur); + // 移除监听 + // return () => { + // editorRef.current?.removeEventListener('focus', focus); + // editorRef.current?.removeEventListener('blur', blur); + // }; + }, []); + + + useEffect(() => { + if (editorRef.current) { // eg: // editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyL, () => { // }); - shortcutKey?.(editorRef.current, monaco); + shortcutKey?.(editorRef.current, monaco, isActive); } }, [editorRef.current, isActive]); diff --git a/chat2db-client/src/components/SearchResult/components/ScreeningResult/index.less b/chat2db-client/src/components/SearchResult/components/ScreeningResult/index.less new file mode 100644 index 000000000..3d7829a81 --- /dev/null +++ b/chat2db-client/src/components/SearchResult/components/ScreeningResult/index.less @@ -0,0 +1,42 @@ +.screeningResult{ + display: flex; + align-items: center; + height: 18px; + padding: 4px 0px; + border-bottom: 1px solid var(--color-border); + .whereBox,.orderByBox{ + width: 50%; + display: flex; + align-items: center; + height: 100%; + margin: 0px 4px; + .titleBox{ + display: flex; + align-items: center; + margin-right: 10px; + width: fit-content; + flex-shrink: 0; + } + .titleIcon{ + margin-right: 4px; + } + .title{ + color: var(--color-text-secondary); + flex-shrink: 0; + font-weight: 500; + } + .activeTitle{ + color: var(--color-primary-text); + } + .monacoEditor{ + flex: 1; + height: 100%; + width: 0px; + } + } + :global { + .decorationsOverviewRuler{ + display: none !important; + } + } +} \ No newline at end of file diff --git a/chat2db-client/src/components/SearchResult/components/ScreeningResult/index.tsx b/chat2db-client/src/components/SearchResult/components/ScreeningResult/index.tsx new file mode 100644 index 000000000..1030f4ca5 --- /dev/null +++ b/chat2db-client/src/components/SearchResult/components/ScreeningResult/index.tsx @@ -0,0 +1,139 @@ +import React, { memo, useEffect, useContext } from 'react'; +import classnames from 'classnames'; +import styles from './index.less'; +import Iconfont from '@/components/Iconfont'; +import SingleFileMonacoEditor from '@/components/SingleFileMonacoEditor'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { IExecuteSqlParams } from '@/service/sql'; +import { Context } from '@/components/SearchResult'; + +interface IProps { + className?: string; + promptWord: any[]; + getTableData: (params?: Partial) => void; +} + +const keywordHintList = [ + 'AND', + 'OR', + 'NOT', + 'IS', + 'NULL', + 'IN', + 'IS NOT NULL', + 'IS NULL', + 'IS NOT', + 'NOT IN', + 'EXISTS', + 'BETWEEN', + 'LIKE', + 'ASC', + 'DESC', +] + +export default memo((props) => { + const { promptWord, getTableData } = props; + const { notChangedSql } = useContext(Context); + const [isActive, setIsActive] = React.useState(false); + const keywordHintRef = React.useRef(null); + const fieldHintRef = React.useRef(null); + const whereSingleFileMonacoEditorRef = React.useRef(null); + const orderBySingleFileMonacoEditorRef = React.useRef(null); + + useEffect(() => { + keywordHintRef.current && keywordHintRef.current.dispose(); + fieldHintRef.current && fieldHintRef.current.dispose(); + if (isActive) { + registerPromptWord(); + } + }, [promptWord, isActive]); + + const registerPromptWord = () => { + const suggestions = promptWord.slice(1).map((item) => { + return { + insertText: item.name, + kind: monaco.languages.CompletionItemKind.Field, + label: item.name, + }; + }); + + const provideCompletionItems: any = () => { + return { + suggestions, + }; + }; + + fieldHintRef.current = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems, + triggerCharacters: [], + }); + + keywordHintRef.current = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: () => { + return { + suggestions: keywordHintList.map((item) => { + return { + insertText: item, + kind: monaco.languages.CompletionItemKind.Keyword, + label: item, + }; + }), + }; + }, + triggerCharacters: [], + }); + }; + + const search = () => { + const whereValue = whereSingleFileMonacoEditorRef.current?.getAllContent().trim() || ''; + const orderByValue = orderBySingleFileMonacoEditorRef.current?.getAllContent().trim() || ''; + let sql = whereValue ? notChangedSql + ' WHERE ' + whereValue : notChangedSql; + sql = orderByValue ? sql + ' ORDER BY ' + orderByValue : sql; + getTableData({ sql }); + }; + + const focusChange = (_isActive: boolean) => { + setIsActive(_isActive); + }; + + return ( +
+
+
+ +
+ WHERE +
+
+ +
+
+
+ +
+ ORDER BY +
+
+ +
+
+ ); +}); diff --git a/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx index 8e7e25952..dc24967c6 100644 --- a/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx @@ -6,6 +6,8 @@ import classnames from 'classnames'; import lodash from 'lodash'; import { v4 as uuid } from 'uuid'; import i18n from '@/i18n'; +import ScreeningResult from '@/components/SearchResult/components/ScreeningResult'; +// import { Context } from '@/components/SearchResult'; // 样式 import styles from './index.less'; @@ -49,6 +51,7 @@ interface ITableProps { executeSqlParams: any; tableBoxId: string; isActive?: boolean; + concealTabHeader?: boolean; // concealTabHeader 是否隐藏tab头部, 目前来说隐藏头部都是单表查询。需要显示筛选 } interface IViewTableCellData { @@ -102,7 +105,8 @@ const defaultPaginationConfig: IResultConfig = { export const TableContext = React.createContext({} as any); export default function TableBox(props: ITableProps) { - const { className, outerQueryResultData, isActive } = props; + // const {} = useContext(Context); + const { className, outerQueryResultData, isActive, concealTabHeader } = props; const [viewTableCellData, setViewTableCellData] = useState(null); const [, contextHolder] = message.useMessage(); const [paginationConfig, setPaginationConfig] = useState(defaultPaginationConfig); @@ -246,7 +250,6 @@ export default function TableBox(props: ITableProps) { function monacoEditorEditData() { const editorData = monacoEditorRef?.current?.getAllContent(); const { rowId, colId } = viewTableCellData!; - console.log('oldTableData', oldTableData); oldTableData.forEach((item) => { if (item[colNoCode] === rowId) { if (item[colId] !== editorData) { @@ -681,12 +684,8 @@ export default function TableBox(props: ITableProps) { /> ) : ( <> -
- {content} -
-
- {content} -
+
{content}
+
{content}
)} @@ -1052,25 +1051,30 @@ export default function TableBox(props: ITableProps) { - -
- {allDataReady && ( - <> - {tableLoading && } -

{i18n('common.text.noData')}

}} - isStickyHead - stickyTop={31} - {...pipeline.getProps()} - /> - - )} -
-
+ {concealTabHeader && } + {isActive ? ( + +
+ {allDataReady && ( + <> + {tableLoading && } +

{i18n('common.text.noData')}

}} + isStickyHead + stickyTop={31} + {...pipeline.getProps()} + /> + + )} +
+
+ ) : ( +
+ )} {renderContent()} {contextHolder}
- : false ); } diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 65d882e79..65daa8e1f 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -48,6 +48,7 @@ export interface ISearchResultRef { interface IContext { // 这里不用ref的话,会导致切换时闪动 activeTabId: string; + notChangedSql: string; } export const Context = createContext({} as any); @@ -58,6 +59,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = const [tableLoading, setTableLoading] = useState(false); const controllerRef = useRef(); const [activeTabId, setActiveTabId] = useState(''); + const [notChangedSql, setNotChangedSql] = useState(''); useEffect(() => { if (sql) { @@ -97,6 +99,9 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = })); setResultDataList(sqlResult); + if(!notChangedSql){ + setNotChangedSql(_sql); + } }) .finally(() => { setTableLoading(false); @@ -121,6 +126,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = key={queryResultData.uuid} outerQueryResultData={queryResultData} executeSqlParams={props.executeSqlParams} + concealTabHeader={concealTabHeader} /> ) : (
@@ -194,6 +200,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) =
diff --git a/chat2db-client/src/components/SingleFileMonacoEditor/index.less b/chat2db-client/src/components/SingleFileMonacoEditor/index.less new file mode 100644 index 000000000..62484ad0a --- /dev/null +++ b/chat2db-client/src/components/SingleFileMonacoEditor/index.less @@ -0,0 +1,5 @@ +@import '../../styles/var.less'; + +.singleFileMonacoEditor { + height: 18px; +} diff --git a/chat2db-client/src/components/SingleFileMonacoEditor/index.tsx b/chat2db-client/src/components/SingleFileMonacoEditor/index.tsx new file mode 100644 index 000000000..ffc3ed140 --- /dev/null +++ b/chat2db-client/src/components/SingleFileMonacoEditor/index.tsx @@ -0,0 +1,94 @@ +import React, { memo, useCallback, useMemo, ForwardedRef, forwardRef, useImperativeHandle, useRef } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import MonacoEditor, { IExportRefFunction } from '@/components/MonacoEditor'; +import { v4 as uuid } from 'uuid'; + +interface IProps { + className?: string; + handelEnter?: (value: string) => void; + focusChange?: (isActive: boolean) => void; + ref: any; // ref不是写在这里吧??? +} + +export interface ISingleFileMonacoEditorRefFunction { + getAllContent?: () => string; +} + +const options = { + lineNumbers: false, + renderLineHighlight: 'none', + scrollBeyondLastLine: false, + wordWrap: 'off', + minimap: { + enabled: false, + }, + // 不显示滚动条 + scrollbar: { + vertical: 'hidden', + horizontal: 'hidden', + }, + overviewRulerBorder: false, + glyphMargin: false, + folding: false, + lineDecorationsWidth: 0, // 行号宽度 + lineNumbersMinChars: 0, // 行号最小宽度 +}; + +const SingleFileMonacoEditor = memo( + forwardRef((props, ref: ForwardedRef) => { + const { className, handelEnter, focusChange } = props; + const editorRef = useRef(null); + const monacoEditorRef = useRef(null); + + const editorId = useMemo(() => { + return uuid(); + }, []); + + const handleKeydown = useCallback((event) => { + if (event.key === 'Enter' && editorRef.current) { + const controller = editorRef.current.getContribution('editor.contrib.suggestController') as any; + const suggestWidget = controller._widget; + if (suggestWidget && suggestWidget.suggestWidgetVisible.get()) { + return; + } + // 否则,阻止回车键的默认行为 + event.preventDefault(); + const value = monacoEditorRef.current?.getAllContent().trim() || ''; + handelEnter && handelEnter(value); + } + }, []); + + // 监听keydown事件,阻止回车键的默认行为 + const registerShortcutKey = useCallback((_editor, _monaco, isActive) => { + if (isActive) { + editorRef.current = _editor; + window.addEventListener('keydown', handleKeydown); + } else { + window.removeEventListener('keydown', handleKeydown); + } + }, []); + + const getAllContent = () => { + return monacoEditorRef.current?.getAllContent() || ''; + }; + + useImperativeHandle(ref, () => ({ + getAllContent, + })); + + return ( +
+ +
+ ); + }), +); + +export default SingleFileMonacoEditor; diff --git a/chat2db-client/src/layouts/GlobalLayout/index.tsx b/chat2db-client/src/layouts/GlobalLayout/index.tsx index 24add36c0..d14ce7a37 100644 --- a/chat2db-client/src/layouts/GlobalLayout/index.tsx +++ b/chat2db-client/src/layouts/GlobalLayout/index.tsx @@ -1,4 +1,4 @@ -import React, { useLayoutEffect, useState } from 'react'; +import React, { useEffect, useLayoutEffect, useState } from 'react'; import usePollRequestService, { ServiceStatus } from '@/hooks/usePollRequestService'; import i18n, { isEn } from '@/i18n'; import { Button, ConfigProvider, Spin, Tooltip } from 'antd'; @@ -14,10 +14,16 @@ import { GithubOutlined, SyncOutlined, WechatOutlined } from '@ant-design/icons' import { ThemeType } from '@/constants'; import GlobalComponent from '../init/GlobalComponent'; import styles from './index.less'; +import { useUserStore, queryCurUser } from '@/store/user' const GlobalLayout = () => { const [appTheme, setAppTheme] = useTheme(); const [antdTheme, setAntdTheme] = useState({}); + const { curUser } = useUserStore((state)=> { + return { + curUser: state.curUser + } + }) const { serviceStatus, restartPolling } = usePollRequestService({ loopService: service.testService, @@ -34,6 +40,12 @@ const GlobalLayout = () => { monitorOsTheme(); }, []); + useEffect(() => { + if(serviceStatus === ServiceStatus.SUCCESS){ + queryCurUser(); + } + }, [serviceStatus]); + // 监听系统(OS)主题变化 const monitorOsTheme = () => { function change(e: any) { @@ -49,7 +61,7 @@ const GlobalLayout = () => { }; // 等待状态页面 - if (serviceStatus === ServiceStatus.PENDING) { + if (serviceStatus === ServiceStatus.PENDING || curUser === null) { return ; } diff --git a/chat2db-client/src/pages/demo/index.less b/chat2db-client/src/pages/demo/index.less index d9e0b8ec1..19d4cd568 100644 --- a/chat2db-client/src/pages/demo/index.less +++ b/chat2db-client/src/pages/demo/index.less @@ -1,3 +1,3 @@ .introduce{ - height: 100%; + height: 18px; } \ No newline at end of file diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index e32024ef1..928444df6 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -1,13 +1,15 @@ -import React from 'react'; -import styles from './index.less'; -import MonacoEditor from '@/components/MonacoEditor'; +import React, { useEffect } from 'react'; function Test() { - return ( -
- -
- ); + const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjIsImRldmljZSI6ImRlZmF1bHQtZGV2aWNlIiwiZWZmIjoxNzA2ODU0NTMwMDI3LCJyblN0ciI6Ik1RcHRPOUVBVlJlbGRQa1RFN01MZUpLeG5KTGVwRFpaIn0.knOw08E6mwWF_GpkeQ8KflQlfQuNu4jd-_Bgh7EnCj4' + useEffect(() => { + const socket = new WebSocket(`ws://127.0.0.1:10821/api/ws/${token}`); + socket.onopen = () => { + console.log('open'); + socket.send('hello'); + }; + }, []); + return (false); } export default Test; diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index 0a9793df0..8a4045262 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { Button, Form, Input, Tooltip } from 'antd'; -import { userLogin, getUser } from '@/service/user'; +import { userLogin } from '@/service/user'; import LogoImg from '@/assets/logo/logo.png'; import styles from './index.less'; import Setting from '@/blocks/Setting'; @@ -8,6 +8,7 @@ import Iconfont from '@/components/Iconfont'; import i18n from '@/i18n'; // import { useNavigate } from 'react-router-dom'; import { logoutClearSomeLocalStorage, navigate } from '@/utils'; +import { queryCurUser } from '@/store/user' interface IFormData { userName: string; @@ -19,17 +20,12 @@ const Login: React.FC = () => { logoutClearSomeLocalStorage(); }, []); - // const navigate = useNavigate(); const handleLogin = async (formData: IFormData) => { const token = await userLogin(formData); - getUser().then((res) => { - // 向cookie中写入当前用户id - const date = new Date('2030-12-30 12:30:00').toUTCString(); - document.cookie = `CHAT2DB.USER_ID=${res?.id};Expires=${date}`; - if (token && res) { - navigate('/'); - } - }); + const res = await queryCurUser(); + if (token && res) { + navigate('/'); + } }; return ( diff --git a/chat2db-client/src/pages/main/dashboard/chart/bar/index.tsx b/chat2db-client/src/pages/main/dashboard/chart/bar/index.tsx index b56d7598c..95e8cb452 100644 --- a/chat2db-client/src/pages/main/dashboard/chart/bar/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart/bar/index.tsx @@ -29,6 +29,7 @@ const BarChart = (props: IProps, ref) => { type: 'bar', }, ], + tooltip: {}, }), [props.data], ); diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 5aa4e6326..98be93503 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -7,9 +7,9 @@ import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; import i18n from '@/i18n'; -import { getUser, userLogout } from '@/service/user'; +import { userLogout } from '@/service/user'; import { INavItem } from '@/typings/main'; -import { ILoginUser, IRole } from '@/typings/user'; +import { IRole } from '@/typings/user'; // ----- hooks ----- import getConnectionEnvList from './functions/getConnection'; @@ -17,6 +17,7 @@ import getConnectionEnvList from './functions/getConnection'; // ----- store ----- import { useMainStore, setMainPageActiveTab } from '@/pages/main/store/main'; import { getConnectionList } from '@/pages/main/store/connection'; +import { useUserStore, setCurUser } from '@/store/user'; // ----- block ----- import Workspace from './workspace'; @@ -65,8 +66,12 @@ const initNavConfig: INavItem[] = [ function MainPage() { const navigate = useNavigate(); + const { userInfo } = useUserStore(state => { + return { + userInfo: state.curUser + } + }); const [navConfig, setNavConfig] = useState(initNavConfig); - const [userInfo, setUserInfo] = useState(); const mainPageActiveTab = useMainStore((state) => state.mainPageActiveTab); const [activeNavKey, setActiveNavKey] = useState( __ENV__ === 'desktop' ? mainPageActiveTab : window.location.pathname.split('/')[1] || mainPageActiveTab, @@ -96,14 +101,11 @@ function MainPage() { } }, [activeNavKey]); - // 这里如果社区版没有登陆可能需要后端来个重定向? const handleInitPage = async () => { const cloneNavConfig = [...navConfig]; - const res = await getUser(); - if (res) { - setUserInfo(res); + if (userInfo) { const hasTeamIcon = cloneNavConfig.find((i) => i.key === 'team'); - if (res.admin && !hasTeamIcon) { + if (userInfo.admin && !hasTeamIcon) { cloneNavConfig.splice(3, 0, { key: 'team', icon: '\ue64b', @@ -113,7 +115,7 @@ function MainPage() { name: i18n('team.title'), }); } - if (!res.admin && hasTeamIcon) { + if (!userInfo.admin && hasTeamIcon) { cloneNavConfig.splice(3, 1); } } @@ -131,7 +133,7 @@ function MainPage() { const handleLogout = () => { userLogout().then(() => { - setUserInfo(undefined); + setCurUser(undefined); navigate('/login'); }); }; diff --git a/chat2db-client/src/service/user.ts b/chat2db-client/src/service/user.ts index 66ee1ef89..c3674ce73 100644 --- a/chat2db-client/src/service/user.ts +++ b/chat2db-client/src/service/user.ts @@ -1,6 +1,6 @@ import createRequest from './base'; import { IPageParams, IPageResponse } from '@/typings'; -import { ILoginUser, IUser } from '@/typings/user'; +import { IUserVO, IUser } from '@/typings/user'; /** 用户登录接口 */ const userLogin = createRequest<{ userName: string; password: string }, boolean>('/api/oauth/login_a', { @@ -13,7 +13,7 @@ const userLogout = createRequest('/api/oauth/logout_a', { }); /** 获取用户信息 */ -const getUser = createRequest('/api/oauth/user_a', { method: 'get' }); +const getUser = createRequest('/api/oauth/user_a', { method: 'get' }); /** 获取用户列表信息 */ const getUserList = createRequest>('/api/user/list', { diff --git a/chat2db-client/src/store/user/index.ts b/chat2db-client/src/store/user/index.ts new file mode 100644 index 000000000..b44b35e5f --- /dev/null +++ b/chat2db-client/src/store/user/index.ts @@ -0,0 +1,45 @@ +import { StoreApi } from 'zustand'; +import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; +import { devtools } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; +import { IUserVO } from '@/typings/user'; +import { getUser } from '@/service/user'; + +export interface IUserStore { + curUser?: IUserVO | null; +} + +const initUserStore: IUserStore = { + curUser: null, +}; + +/** + * 用户 store + */ +export const useUserStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( + devtools(() => initUserStore), + shallow, +); + +/** + * + * @param curUser 设置当前用户 + */ + +export const setCurUser = (curUser?: IUserVO) => { + useUserStore.setState({ curUser }); +}; + +/** + * 获取当前用户 + */ + +export const queryCurUser = async () => { + // null 表示在padding,返回 void 0(undefined)表示未登录 + const curUser = await getUser() || void 0; + useUserStore.setState({ curUser }); + // 向cookie中写入当前用户id + const date = new Date('2030-12-30 12:30:00').toUTCString(); + document.cookie = `CHAT2DB.USER_ID=${curUser?.id};Expires=${date}`; + return curUser +}; diff --git a/chat2db-client/src/typings/user.ts b/chat2db-client/src/typings/user.ts index e8dcc238e..bed4a740e 100644 --- a/chat2db-client/src/typings/user.ts +++ b/chat2db-client/src/typings/user.ts @@ -13,18 +13,10 @@ export interface IUser { role?: IRole; } -export interface ILoginUser { - /** - * Is it an administrator - */ - admin?: boolean; - /** - * 用户id - */ - id?: number; - /** - * 昵称 - */ - nickName?: string; - roleCode: IRole; -} +export type IUserVO = { + admin: boolean; + id : number; + nickName: string; + roleCode: string; + token: string; +} | null diff --git a/chat2db-client/src/utils/IntelliSense/database.ts b/chat2db-client/src/utils/IntelliSense/database.ts index ddb251dff..915100e32 100644 --- a/chat2db-client/src/utils/IntelliSense/database.ts +++ b/chat2db-client/src/utils/IntelliSense/database.ts @@ -1,6 +1,10 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import i18n from '@/i18n'; +export const resetSenseDatabase = () => { + intelliSenseDatabase.dispose(); +} + let intelliSenseDatabase = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: () => { return { suggestions: [] }; @@ -21,7 +25,7 @@ const checkTableContext = (text) => { }; const registerIntelliSenseDatabase = (databaseName: Array<{ name: string; dataSourceName: string }>) => { - intelliSenseDatabase.dispose(); + resetSenseDatabase(); intelliSenseDatabase = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', '.'], provideCompletionItems: (model, position) => { diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts index a39c63376..03bf909c2 100644 --- a/chat2db-client/src/utils/IntelliSense/field.ts +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -6,13 +6,17 @@ let fieldList: Record> = {}; /** 当前库下的表 */ let intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { - provideCompletionItems: (model, position) => { + provideCompletionItems: () => { return { suggestions: [], }; }, }); +export const resetSenseField = () => { + intelliSenseField.dispose(); +} + const addIntelliSenseField = async (props: { tableName: string; dataSourceId: number; @@ -46,7 +50,7 @@ function checkFieldContext(text) { } const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseName, schemaName) => { - intelliSenseField.dispose(); + resetSenseField(); fieldList = {}; intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', ',', '.', '('], diff --git a/chat2db-client/src/utils/IntelliSense/index.ts b/chat2db-client/src/utils/IntelliSense/index.ts index d41fb8682..9586b859a 100644 --- a/chat2db-client/src/utils/IntelliSense/index.ts +++ b/chat2db-client/src/utils/IntelliSense/index.ts @@ -1,17 +1,22 @@ -import { intelliSenseKeyword, registerIntelliSenseKeyword } from './keyword'; -import { intelliSenseDatabase, registerIntelliSenseDatabase } from './database'; -import { intelliSenseTable, registerIntelliSenseTable } from './table'; -import { intelliSenseField, registerIntelliSenseField } from './field'; -import { intelliSenseView, registerIntelliSenseView } from './view'; +import { intelliSenseKeyword, registerIntelliSenseKeyword, resetSenseKeyword } from './keyword'; +import { intelliSenseDatabase, registerIntelliSenseDatabase, resetSenseDatabase } from './database'; +import { intelliSenseTable, registerIntelliSenseTable, resetSenseTable } from './table'; +import { intelliSenseField, registerIntelliSenseField, resetSenseField } from './field'; +import { intelliSenseView, registerIntelliSenseView, resetSenseView } from './view'; export { intelliSenseKeyword, registerIntelliSenseKeyword, + resetSenseKeyword, intelliSenseDatabase, registerIntelliSenseDatabase, + resetSenseDatabase, intelliSenseTable, registerIntelliSenseTable, + resetSenseTable, intelliSenseField, registerIntelliSenseField, + resetSenseField, intelliSenseView, registerIntelliSenseView, + resetSenseView, }; diff --git a/chat2db-client/src/utils/IntelliSense/keyword.ts b/chat2db-client/src/utils/IntelliSense/keyword.ts index 9a7a9a415..182e717e5 100644 --- a/chat2db-client/src/utils/IntelliSense/keyword.ts +++ b/chat2db-client/src/utils/IntelliSense/keyword.ts @@ -32,6 +32,10 @@ const getSQLFunctions = (functions: string[]) => { })); }; +export const resetSenseKeyword = () => { + intelliSenseKeyword.dispose(); +} + let intelliSenseKeyword = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: () => { return { suggestions: [] }; @@ -39,7 +43,7 @@ let intelliSenseKeyword = monaco.languages.registerCompletionItemProvider('sql', }); const registerIntelliSenseKeyword = (databaseCode?: DatabaseTypeCode) => { - intelliSenseKeyword.dispose(); + resetSenseKeyword(); intelliSenseKeyword = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', '('], // 触发提示的字符 provideCompletionItems: (model, position) => { diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index 4afa7fd4f..97039a3e5 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -4,12 +4,14 @@ import { addIntelliSenseField } from './field'; import i18n from '@/i18n'; import { compatibleDataBaseName } from '../database'; +export const resetSenseTable = () => { + intelliSenseTable.dispose(); +}; + /** 当前库下的表 */ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { - provideCompletionItems: (model, position) => { - return { - suggestions: [], - }; + provideCompletionItems: () => { + return { suggestions: [] }; }, }); @@ -46,7 +48,7 @@ const registerIntelliSenseTable = ( return; }); - intelliSenseTable.dispose(); + resetSenseTable(); intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', '.'], provideCompletionItems: (model, position) => { diff --git a/chat2db-client/src/utils/IntelliSense/view.ts b/chat2db-client/src/utils/IntelliSense/view.ts index cf4da9232..65ac4202d 100644 --- a/chat2db-client/src/utils/IntelliSense/view.ts +++ b/chat2db-client/src/utils/IntelliSense/view.ts @@ -1,13 +1,15 @@ -import { DatabaseTypeCode } from '@/constants'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import i18n from '@/i18n'; +export const resetSenseView = () => { + intelliSenseView.dispose(); + +} + /** 当前库下的表 */ let intelliSenseView = monaco.languages.registerCompletionItemProvider('sql', { - provideCompletionItems: (model, position) => { - return { - suggestions: [], - }; + provideCompletionItems: () => { + return { suggestions: [] }; }, }); @@ -28,7 +30,7 @@ const registerIntelliSenseView = ( viewList: string[], databaseName?: string | null, ) => { - intelliSenseView.dispose(); + resetSenseView(); intelliSenseView = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' '], provideCompletionItems: (model, position) => { diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java index 90a0e2fa9..fef4d734a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java @@ -21,6 +21,7 @@ import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SortUtils; import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringUtils; public class DB2MetaData extends DefaultMetaService implements MetaData { @@ -156,5 +157,9 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .defaultValues(DB2DefaultValueEnum.getDefaultValues()) .build(); } + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java index 7bd1082e8..f5c725614 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java @@ -4,6 +4,7 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.*; +import java.util.stream.Collectors; import ai.chat2db.plugin.h2.builder.H2SqlBuilder; import ai.chat2db.spi.MetaData; @@ -14,6 +15,7 @@ import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SortUtils; import jakarta.validation.constraints.NotEmpty; +import org.apache.commons.lang3.StringUtils; public class H2Meta extends DefaultMetaService implements MetaData { @@ -198,4 +200,9 @@ public SqlBuilder getSqlBuilder() { return new H2SqlBuilder(); } + + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java index 9cc1b7e48..4716d88e7 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java @@ -2,8 +2,19 @@ import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.sql.SQLExecutor; +import com.google.common.collect.Lists; + +import java.sql.Connection; +import java.util.List; + public class MongodbMetaData extends DefaultMetaService implements MetaData { + @Override + public List databases(Connection connection) { + return Lists.newArrayList(); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json index 3b0aa3727..29d55a1c4 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json +++ b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json @@ -1,6 +1,6 @@ { "dbType": "MONGODB", - "supportDatabase": true, + "supportDatabase": false, "supportSchema": true, "driverConfigList": [ { diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java index 9081ec7e9..1a97c843b 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java @@ -111,7 +111,7 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { } // append reorder column - script.append("\t").append(buildGenerateReorderColumnSql(oldTable, newTable)); + script.append(buildGenerateReorderColumnSql(oldTable, newTable)); if (script.length() > 2) { script = new StringBuilder(script.substring(0, script.length() - 2)); diff --git a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseMetaData.java b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseMetaData.java index 30eb940e9..2c1020fa8 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseMetaData.java @@ -4,4 +4,6 @@ import ai.chat2db.spi.jdbc.DefaultMetaService; public class OceanBaseMetaData extends DefaultMetaService implements MetaData { + + } diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java index 4d46c647c..26e566edd 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java @@ -302,4 +302,8 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .build(); } + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OrderByParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OrderByParam.java new file mode 100644 index 000000000..8110677b3 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OrderByParam.java @@ -0,0 +1,47 @@ +package ai.chat2db.server.domain.api.param; + +import ai.chat2db.spi.model.OrderBy; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + + +@Data +public class OrderByParam { + + /** + * 控制台id + */ + @NotNull + private Long consoleId; + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * DB名称 + */ + private String databaseName; + + + /** + * schema名称 + */ + private String schemaName; + + + /** + * origin sql + */ + private String originSql; + + + /** + * 排序字段 + */ + private List orderByList; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java index 2195c55ac..cfee4cd12 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java @@ -2,6 +2,7 @@ import ai.chat2db.server.domain.api.param.DlCountParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; +import ai.chat2db.server.domain.api.param.OrderByParam; import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -50,4 +51,12 @@ public interface DlTemplateService { */ DataResult updateSelectResult(UpdateSelectResultParam param); + + /** + * + * @param param + * @return + */ + DataResult getOrderBySql(OrderByParam param); + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 1cbdd17de..3e6aff9e3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -10,6 +10,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import ai.chat2db.server.domain.api.param.*; +import ai.chat2db.spi.SqlBuilder; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.PagerUtils; import com.alibaba.druid.sql.SQLUtils; @@ -17,11 +19,6 @@ import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.parser.ParserException; -import ai.chat2db.server.domain.api.param.DlCountParam; -import ai.chat2db.server.domain.api.param.DlExecuteParam; -import ai.chat2db.server.domain.api.param.SelectResultOperation; -import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.OperationLogService; @@ -294,6 +291,13 @@ public DataResult updateSelectResult(UpdateSelectResultParam param) { return DataResult.of(stringBuilder.toString()); } + @Override + public DataResult getOrderBySql(OrderByParam param) { + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); + String orderSql = sqlBuilder.buildOrderBySql(param.getOriginSql(),param.getOrderByList()); + return DataResult.of(orderSql); + } + private List getPrimaryColumns(UpdateSelectResultParam param) { List
headerList = param.getHeaderList(); if (CollectionUtils.isEmpty(headerList)) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java index fa252c438..ce829f212 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java @@ -1,11 +1,7 @@ package ai.chat2db.server.domain.repository; -import ai.chat2db.server.domain.repository.entity.DataSourceDO; -import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; import ai.chat2db.server.tools.common.model.ConfigJson; import ai.chat2db.server.tools.common.util.ConfigUtils; -import cn.hutool.core.lang.UUID; -import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.config.GlobalConfig; diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 118baaa25..573a6105f 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -1,14 +1,8 @@ package ai.chat2db.server.start; -import ai.chat2db.server.tools.common.enums.ModeEnum; -import ai.chat2db.server.tools.common.model.ConfigJson; +import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.tools.common.util.ConfigUtils; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; -import cn.hutool.core.lang.UUID; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @@ -17,6 +11,8 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.stereotype.Indexed; +import java.util.concurrent.CompletableFuture; + /** * 启动类 * @@ -32,7 +28,10 @@ public class Application { public static void main(String[] args) { - //ConfigUtils.pid(); + ConfigUtils.initProcess(); + new Thread(() -> { + Dbutils.init(); + }).start(); SpringApplication.run(Application.class, args); } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java index 2d486f918..2c178c95d 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java @@ -89,6 +89,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl // 代表用户可能被删除了 return true; } + loginUser.setToken(userId.toString()); ContextUtils.setContext(Context.builder() .loginUser(loginUser) diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java index a7c718264..be064187d 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java @@ -44,4 +44,7 @@ public class LoginUser implements Serializable { * @see RoleCodeEnum */ private String roleCode; + + + private String token; } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java index 7ad4e6868..8aa2d62bf 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java @@ -2,6 +2,7 @@ import ai.chat2db.server.tools.common.model.ConfigJson; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.UUID; import com.alibaba.fastjson2.JSON; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -25,6 +26,9 @@ public class ConfigUtils { public static File configFile; private static ConfigJson config = null; + public static File clientIdFile; + private static String clientId = null; + static { String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); if (APP_PATH != null) { @@ -39,6 +43,14 @@ public class ConfigUtils { if (!configFile.exists()) { FileUtil.writeUtf8String(JSON.toJSONString(new ConfigJson()), configFile); } + + clientIdFile = new File( + CONFIG_BASE_PATH + File.separator + "config" + File.separator + "client_uuid"); + if (!clientIdFile.exists()) { + String uuid = UUID.fastUUID().toString(true); + FileUtil.writeUtf8String(uuid, clientIdFile); + clientId = uuid; + } } public static void updateVersion(String version) { @@ -61,6 +73,7 @@ public static String getLocalVersion() { version = StringUtils.trim(FileUtil.readUtf8String(versionFile)); return version; } + public static String getLatestLocalVersion() { if (versionFile == null) { log.warn("VERSION_FILE is null"); @@ -77,6 +90,13 @@ public static ConfigJson getConfig() { return config; } + public static String getClientId() { + if (clientId == null) { + clientId = StringUtils.trim(FileUtil.readUtf8String(clientIdFile)); + } + return clientId; + } + public static void setConfig(ConfigJson config) { String stringConfigJson = JSON.toJSONString(config); FileUtil.writeUtf8String(stringConfigJson, configFile); @@ -87,9 +107,6 @@ public static void setConfig(ConfigJson config) { private static String getAppPath() { try { String jarPath = System.getProperty("project.path"); -// log.info("user home: {}", System.getProperty("user.home")); -// log.info("project.path: {}", System.getProperty("project.path")); -// log.info("jarPath: {}", jarPath); return FileUtil.getParent(jarPath, 4); } catch (Exception e) { log.error("getAppPath error", e); @@ -97,38 +114,36 @@ private static String getAppPath() { } } - public static void pid() { + public static void initProcess() { try { - log.error("pidinit"); ProcessHandle currentProcess = ProcessHandle.current(); long pid = currentProcess.pid(); - log.error("pid:{}", pid); String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); - File pidFile = new File(CONFIG_BASE_PATH + File.separator + "config" + File.separator + environment + "pid"); + File pidFile = new File(CONFIG_BASE_PATH + File.separator + "config" + File.separator + environment + "app.pid"); if (!pidFile.exists()) { FileUtil.writeUtf8String(String.valueOf(pid), pidFile); } else { String oldPid = FileUtil.readUtf8String(pidFile); - log.error("oldPid:{}", oldPid); + log.info("oldPid:{}", oldPid); if (StringUtils.isNotBlank(oldPid)) { Optional processHandle = ProcessHandle.of(Long.parseLong(oldPid)); - log.error("processHandle:{}", JSON.toJSONString(processHandle)); + //log.error("processHandle:{}", JSON.toJSONString(processHandle)); processHandle.ifPresent(handle -> { ProcessHandle.Info info = handle.info(); String[] arguments = info.arguments().orElse(null); - log.error("arguments:{}", JSON.toJSONString(arguments)); + log.info("arguments:{}", JSON.toJSONString(arguments)); if (arguments == null) { return; } for (String argument : arguments) { if (StringUtils.equals("chat2db-server-start.jar", argument)) { handle.destroy(); - log.error("destroy old process--------"); + log.info("destroy old process--------"); break; } - if (StringUtils.equals("application", argument)) { + if (argument.contains("Application")) { handle.destroy(); - log.error("destroy old process--------"); + log.info("destroy old process--------"); break; } } @@ -138,8 +153,8 @@ public static void pid() { FileUtil.writeUtf8String(String.valueOf(pid), pidFile); } - }catch (Exception e){ - log.error("updatePid error",e); + } catch (Exception e) { + log.error("updatePid error", e); } } diff --git a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java index fef6f9b73..519fa42fd 100644 --- a/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java @@ -111,6 +111,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl return true; } + loginUser.setToken(StpUtil.getTokenValue()); ContextUtils.setContext(Context.builder() .loginUser(loginUser) .build()); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml index a6e7589f1..cf32aae04 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml @@ -96,11 +96,13 @@ org.springframework spring-context - - com.manticore-projects.jsqlformatter - jsqlformatter - 1.0.0 + com.github.vertical-blank + sql-formatter + + + org.springframework.boot + spring-boot-starter-websocket diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index d7a6f18fe..2197d84e1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -8,23 +8,23 @@ import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.DlExecuteParam; +import ai.chat2db.server.domain.api.param.OrderByParam; import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; -import ai.chat2db.server.web.api.controller.rdb.request.DdlCountRequest; -import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; -import ai.chat2db.server.web.api.controller.rdb.request.DmlTableRequest; -import ai.chat2db.server.web.api.controller.rdb.request.SelectResultUpdateRequest; +import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.server.web.api.http.GatewayClientService; import ai.chat2db.server.web.api.http.request.SqlExecuteHistoryCreateRequest; import ai.chat2db.server.web.api.util.ApplicationContextUtil; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.sql.Chat2DBContext; import com.google.common.collect.Lists; @@ -71,7 +71,7 @@ public ListResult manage(@RequestBody DmlRequest request) { ListResult resultDTOListResult = dlTemplateService.execute(param); List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); String type = Chat2DBContext.getConnectInfo().getDbType(); - String clientId = getApiKey(); + String clientId = getClientId(); String sqlContent = request.getSql(); executorService.submit(() -> { try { @@ -103,11 +103,11 @@ private void addOperationLog(String clientId, String sqlType, String sqlContent, * * @return */ - private String getApiKey() { + private String getClientId() { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { - return null; + return ConfigUtils.getClientId(); } return keyConfig.getContent(); } @@ -126,10 +126,12 @@ public ListResult executeTable(@RequestBody DmlTableRequest req if (DataSourceTypeEnum.MONGODB.getCode().equals(type)) { param.setSql("db." + request.getTableName() + ".find()"); } else { - param.setSql("select * from " + request.getTableName()); + MetaData metaData = Chat2DBContext.getMetaData(); + // 拼接`tableName`,避免关键字被占用问题 + param.setSql("select * from " + metaData.getMetaDataName(request.getTableName())); } return dlTemplateService.execute(param) - .map(rdbWebConverter::dto2vo); + .map(rdbWebConverter::dto2vo); } /** @@ -148,7 +150,7 @@ public DataResult executeSelectResultUpdate(@RequestBody DmlReq ExecuteResultVO executeResultVO = rdbWebConverter.dto2vo(result.getData()); String type = Chat2DBContext.getConnectInfo().getDbType(); String sqlContent = request.getSql(); - String clientId = getApiKey(); + String clientId = getClientId(); executorService.submit(() -> { try { addOperationLog(clientId, type, sqlContent, result.getErrorMessage(), result.getSuccess(), Lists.newArrayList(executeResultVO)); @@ -166,6 +168,15 @@ public DataResult getUpdateSelectResultSql(@RequestBody SelectResultUpda return dlTemplateService.updateSelectResult(param); } + + @RequestMapping(value = "/get_order_by_sql", method = {RequestMethod.POST, RequestMethod.PUT}) + public DataResult getOrderBySql(@RequestBody OrderByRequest request) { + + OrderByParam param = rdbWebConverter.request2param(request); + + return dlTemplateService.getOrderBySql(param); + } + /** * 增删改查等数据运维 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java index 035245688..9ba1d3063 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java @@ -27,8 +27,8 @@ public class TriggerController { public WebPageResult list(@Valid TriggerPageRequest request) { ListResult listResult = triggerService.triggers(request.getDatabaseName(), request.getSchemaName()); Long total = CollectionUtils.isNotEmpty(listResult.getData()) ? Long.valueOf(listResult.getData().size()) : 0L; - return WebPageResult.of(listResult.getData(), total, 1, - listResult.getData().size()); + Integer pageSize = listResult.getData() != null ? listResult.getData().size() : 0; + return WebPageResult.of(listResult.getData(), total, 1, pageSize); } @GetMapping("/detail") diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java index 7fdf01d23..3475f33bc 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java @@ -43,7 +43,8 @@ public class ViewController { public WebPageResult list(@Valid TableBriefQueryRequest request) { ListResult tableDTOPageResult = viewService.views(request.getDatabaseName(), request.getSchemaName()); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); - return WebPageResult.of(tableVOS, Long.valueOf(tableVOS.size()), 1, tableVOS.size()); + Integer pageSize = tableDTOPageResult.getData() != null ? tableDTOPageResult.getData().size() : 0; + return WebPageResult.of(tableVOS, Long.valueOf(tableVOS.size()), 1, pageSize); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java index 788a814cb..b04663dc9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java @@ -42,6 +42,14 @@ public abstract class RdbWebConverter { public abstract DlExecuteParam request2param(DmlRequest request); + /** + * 参数转换 + * + * @param request + * @return + */ + public abstract OrderByParam request2param(OrderByRequest request); + /** * 参数转换 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java index b6a879cf2..593426a15 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java @@ -44,4 +44,5 @@ public class DmlRequest extends DataSourceBaseRequest implements DataSourceConso * 只有select语句才有 */ private Boolean pageSizeAll; + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/OrderByRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/OrderByRequest.java new file mode 100644 index 000000000..e48b5a0bd --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/OrderByRequest.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import ai.chat2db.spi.model.OrderBy; +import lombok.Data; + +import java.util.List; + +@Data +public class OrderByRequest extends DataSourceBaseRequest implements DataSourceBaseRequestInfo { + + /** + * origin sql + */ + private String originSql; + + /** + * 排序字段 + */ + private List orderByList; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java index d7b8e0b29..97e3b1e6c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java @@ -3,7 +3,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.sql.request.SqlFormatRequest; -import com.manticore.jsqlformatter.JSQLFormatter; +import com.github.vertical_blank.sqlformatter.SqlFormatter; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,7 +27,7 @@ public class SqlController { public DataResult list(@Valid SqlFormatRequest sqlFormatRequest) { String sql = sqlFormatRequest.getSql(); try { - sql = JSQLFormatter.format(sql); + sql = SqlFormatter.format(sql); } catch (Exception e) { // ignore } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index 54b73e8c1..78b72e963 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -204,13 +204,15 @@ public DataResult schemaEsSearch(EsTableSchemaRequest req * @return */ public DataResult checkInWhite(WhiteListRequest whiteListRequest) { - DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/whitelist/check") - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .addQuery(whiteListRequest) - .execute(new TypeReference<>() { - }); - return result; + // 去掉白名单 + return DataResult.of(false); +// DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/whitelist/check") +// .connectTimeout(Duration.ofMillis(5000)) +// .readTimeout(Duration.ofMillis(10000)) +// .addQuery(whiteListRequest) +// .execute(new TypeReference<>() { +// }); +// return result; } public ActionResult addOperationLog(SqlExecuteHistoryCreateRequest request) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsConfig.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsConfig.java new file mode 100644 index 000000000..1545dc439 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsConfig.java @@ -0,0 +1,14 @@ +package ai.chat2db.server.web.api.ws; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WsConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsMessage.java new file mode 100644 index 000000000..19564a5d9 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsMessage.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.web.api.ws; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +@Data +public class WsMessage { + + /** + * message id + */ + private String uuid; + + /** + * message content + */ + private JSONObject message; + + /** + * message type + */ + private String actionType; + + + public static class ActionType { + public static final String EXECUTE = "execute"; + public static final String LOGIN = "login"; + public static final String PING = "ping"; + public static final String OPEN_SESSION = "open_session"; + public static final String ERROR = "error"; + public static final String MESSAGE = "message"; + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsResult.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsResult.java new file mode 100644 index 000000000..9bd6e6aa4 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsResult.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.web.api.ws; + + +import ai.chat2db.server.tools.base.wrapper.Result; +import lombok.Data; + +@Data +public class WsResult { + /** + * message id + */ + private String uuid; + + /** + * message content + */ + private Result message; + + /** + * message type + */ + private String actionType; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsServer.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsServer.java new file mode 100644 index 000000000..1d1925269 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsServer.java @@ -0,0 +1,250 @@ +package ai.chat2db.server.web.api.ws; + +import ai.chat2db.server.domain.repository.Dbutils; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.common.model.Context; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; +import ai.chat2db.server.web.api.util.ApplicationContextUtil; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import com.alibaba.fastjson2.JSONObject; +import com.jcraft.jsch.JSchException; +import jakarta.websocket.*; +import jakarta.websocket.server.PathParam; +import jakarta.websocket.server.ServerEndpoint; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.context.support.SpringBeanAutowiringSupport; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +@Component +@ServerEndpoint("/api/ws/{token}") +public class WsServer { + private Session session; + + private static final AtomicInteger OnlineCount = new AtomicInteger(0); + + // concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。 + private static CopyOnWriteArraySet SessionSet = new CopyOnWriteArraySet(); + + private static int num = 0; + + private Timer timer = new Timer(); + + + private Map connectInfoMap = new ConcurrentHashMap<>(); + + + private LoginUser loginUser; + + private WsService wsService; + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("token") String token) throws IOException { + SessionSet.add(session); + this.session = session; + int cnt = OnlineCount.incrementAndGet(); // 在线数加1 + log.info("有连接加入,当前连接数为:{}", cnt); + + heartBeat(session); + this.wsService = ApplicationContextUtil.getBean(WsService.class); + Dbutils.setSession(); + this.loginUser = wsService.doLogin(token); + if (this.loginUser == null) { + ActionResult actionResult = new ActionResult(); + actionResult.setSuccess(false); + actionResult.setErrorCode("LOGIN_FAIL"); + WsResult wsMessage = new WsResult(); + wsMessage.setActionType(WsMessage.ActionType.OPEN_SESSION); + wsMessage.setUuid(token); + wsMessage.setMessage(actionResult); + SendMessage(this.session, wsMessage); + onClose(); + }else { + ActionResult actionResult = new ActionResult(); + actionResult.setSuccess(true); + WsResult wsMessage = new WsResult(); + wsMessage.setActionType(WsMessage.ActionType.OPEN_SESSION); + wsMessage.setUuid(token); + wsMessage.setMessage(actionResult); + SendMessage(this.session, wsMessage); + } + Dbutils.removeSession(); + } + + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() throws IOException { + if (SessionSet.contains(session)) { + SessionSet.remove(this.session); + session.close(); + for (Map.Entry entry : connectInfoMap.entrySet()) { + ConnectInfo connectInfo = entry.getValue(); + if (connectInfo != null) { + Connection connection = connectInfo.getConnection(); + try { + if (connection != null && !connection.isClosed()) { + connection.close(); + } + } catch (SQLException e) { + log.error("close connection error", e); + } + + com.jcraft.jsch.Session session = connectInfo.getSession(); + if (session != null && session.isConnected() && connectInfo.getSsh() != null + && connectInfo.getSsh().isUse()) { + try { + session.delPortForwardingL(Integer.parseInt(connectInfo.getSsh().getLocalPort())); + } catch (JSchException e) { + } + } + } + } + int cnt = OnlineCount.decrementAndGet(); + log.info("有连接关闭,session:{},{}", session, this); + log.info("有连接关闭,当前连接数为:{}", cnt); + } + } + + /** + * 收到客户端消息后调用的方法 + * + * @param message 客户端发送过来的消息 + */ + @OnMessage(maxMessageSize = 1024000) + public void onMessage(String message, Session session) { + CompletableFuture.runAsync(() -> { + WsMessage wsMessage = JSONObject.parseObject(message, WsMessage.class); + // 在这里处理你的消息 + try { + String actionType = wsMessage.getActionType(); + if (WsMessage.ActionType.PING.equalsIgnoreCase(actionType)) { + WsResult wsResult = new WsResult(); + ActionResult actionResult = new ActionResult(); + actionResult.setSuccess(true); + wsResult.setActionType(WsMessage.ActionType.PING); + wsResult.setUuid(wsMessage.getUuid()); + wsResult.setMessage(actionResult); + SendMessage(session, wsResult); + timer.cancel(); + heartBeat(session); + } else { + ContextUtils.setContext(Context.builder() + .loginUser(loginUser) + .build()); + Dbutils.setSession(); + JSONObject jsonObject = wsMessage.getMessage(); + Long dataSourceId = jsonObject.getLong("dataSourceId"); + String databaseName = jsonObject.getString("databaseName"); + String schemaName = jsonObject.getString("schemaName"); + Long consoleId = jsonObject.getLong("consoleId"); + String key = connectInfoKey(dataSourceId, databaseName, schemaName, consoleId); + ConnectInfo connectInfo = connectInfoMap.get(key); + if (connectInfo == null) { + connectInfo = wsService.toInfo(dataSourceId, databaseName, consoleId, schemaName); + connectInfoMap.put(key, connectInfo); + } + Chat2DBContext.putContext(connectInfo); + if (WsMessage.ActionType.EXECUTE.equalsIgnoreCase(actionType)) { + DmlRequest request = jsonObject.toJavaObject(DmlRequest.class); + ListResult result = wsService.execute(request); + WsResult resultMessage = new WsResult(); + resultMessage.setUuid(wsMessage.getUuid()); + resultMessage.setActionType(wsMessage.getActionType()); + resultMessage.setMessage(result); + SendMessage(session, resultMessage); + } + } + } catch (Exception e) { + WsResult wsResult = new WsResult(); + ActionResult actionResult = new ActionResult(); + actionResult.setSuccess(false); + actionResult.setErrorCode(e.getMessage()); + wsResult.setActionType(WsMessage.ActionType.ERROR); + wsResult.setUuid(wsMessage.getUuid()); + wsResult.setMessage(actionResult); + SendMessage(session, wsResult); + } finally { + Chat2DBContext.remove(); + ContextUtils.removeContext(); + Dbutils.removeSession(); + } + }); + + } + + + private String connectInfoKey(Long dataSourceId, String databaseName, String schemaName, Long consoleId) { + return dataSourceId + "_" + databaseName + "_" + schemaName + "_" + consoleId; + } + + + /** + * 出现错误 + * + * @param session + * @param error + */ + @OnError + public void onError(Session session, Throwable error) { + log.error("发生错误:{},Session ID: {}", error.getMessage(), session.getId(), error); + error.printStackTrace(); + } + + /** + * 心跳 + * + * @param session + */ + private void heartBeat(Session session) { + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + try { + onClose(); + } catch (IOException e) { + log.error("发送消息出错:{}", e.getMessage(), e); + } + } + }, 600000); + } + + /** + * 发送消息,实践表明,每次浏览器刷新,session会发生变化。 + * + * @param session + * @param wsResult + */ + public static void SendMessage(Session session, WsResult wsResult) { + try { + if (session.isOpen()) { + session.getBasicRemote().sendText(JSONObject.toJSONString(wsResult)); + } + } catch (IOException e) { + log.error("发送消息出错:{}", e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsService.java new file mode 100644 index 000000000..e2f575ab1 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsService.java @@ -0,0 +1,162 @@ +package ai.chat2db.server.web.api.ws; + +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.model.Config; +import ai.chat2db.server.domain.api.model.DataSource; +import ai.chat2db.server.domain.api.model.User; +import ai.chat2db.server.domain.api.param.DlExecuteParam; +import ai.chat2db.server.domain.api.service.*; +import ai.chat2db.server.domain.core.cache.CacheKey; +import ai.chat2db.server.domain.core.cache.MemoryCacheManage; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; +import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; +import ai.chat2db.server.web.api.http.GatewayClientService; +import ai.chat2db.server.web.api.http.request.SqlExecuteHistoryCreateRequest; +import ai.chat2db.server.web.api.util.ApplicationContextUtil; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.ExecuteResult; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Component +public class WsService { + + @Autowired + private UserService userService; + + + @Autowired + private DataSourceService dataSourceService; + + @Autowired + private DataSourceAccessBusinessService dataSourceAccessBusinessService; + + + @Autowired + private RdbWebConverter rdbWebConverter; + + @Autowired + private DlTemplateService dlTemplateService; + + @Autowired + private GatewayClientService gatewayClientService; + + + public static ExecutorService executorService = Executors.newFixedThreadPool(10); + + public ListResult execute(DmlRequest request) { + DlExecuteParam param = rdbWebConverter.request2param(request); + ListResult resultDTOListResult = dlTemplateService.execute(param); + List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); + String type = Chat2DBContext.getConnectInfo().getDbType(); + String clientId = getApiKey(); + String sqlContent = request.getSql(); + executorService.submit(() -> { + try { + addOperationLog(clientId, type, sqlContent, resultDTOListResult.getErrorMessage(), resultDTOListResult.getSuccess(), resultVOS); + } catch (Exception e) { + // do nothing + } + }); + return ListResult.of(resultVOS); + } + + + public LoginUser doLogin(String token) { + Long userId = RoleCodeEnum.DESKTOP.getDefaultUserId(); + LoginUser loginUser = MemoryCacheManage.computeIfAbsent(CacheKey.getLoginUserKey(userId), () -> { + User user = userService.query(userId).getData(); + if (user == null) { + return null; + } + boolean admin = RoleCodeEnum.ADMIN.getCode().equals(user.getRoleCode()); + + return LoginUser.builder() + .id(user.getId()) + .nickName(user.getNickName()) + .admin(admin) + .roleCode(user.getRoleCode()) + .build(); + }); + + + loginUser.setToken(userId.toString()); + return loginUser; + } + + public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId, String schemaName) { + DataResult result = dataSourceService.queryById(dataSourceId); + DataSource dataSource = result.getData(); + if (!result.success() || dataSource == null) { + throw new ParamBusinessException("dataSourceId"); + } + // Verify permissions + dataSourceAccessBusinessService.checkPermission(dataSource); + ConnectInfo connectInfo = new ConnectInfo(); + connectInfo.setAlias(dataSource.getAlias()); + connectInfo.setUser(dataSource.getUserName()); + connectInfo.setConsoleId(consoleId); + connectInfo.setDataSourceId(dataSourceId); + connectInfo.setPassword(dataSource.getPassword()); + connectInfo.setDbType(dataSource.getType()); + connectInfo.setUrl(dataSource.getUrl()); + connectInfo.setDatabase(database); + connectInfo.setSchemaName(schemaName); + connectInfo.setConsoleOwn(false); + connectInfo.setDriver(dataSource.getDriver()); + connectInfo.setSsh(dataSource.getSsh()); + connectInfo.setSsl(dataSource.getSsl()); + connectInfo.setJdbc(dataSource.getJdbc()); + connectInfo.setExtendInfo(dataSource.getExtendInfo()); + connectInfo.setUrl(dataSource.getUrl()); + connectInfo.setPort(StringUtils.isNotBlank(dataSource.getPort()) ? Integer.parseInt(dataSource.getPort()) : null); + connectInfo.setHost(dataSource.getHost()); + DriverConfig driverConfig = dataSource.getDriverConfig(); + if (driverConfig != null && driverConfig.notEmpty()) { + connectInfo.setDriverConfig(driverConfig); + } + return connectInfo; + } + + + private String getApiKey() { + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); + if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { + return null; + } + return keyConfig.getContent(); + } + + private void addOperationLog(String clientId, String sqlType, String sqlContent, String errorMessage, Boolean isSuccess, List executeResultVOS) { + SqlExecuteHistoryCreateRequest createRequest = new SqlExecuteHistoryCreateRequest(); + createRequest.setClientId(clientId); + createRequest.setErrorMessage(errorMessage); + createRequest.setDatabaseType(sqlType); + createRequest.setSqlContent(sqlContent); + createRequest.setExecuteStatus(isSuccess ? "success" : "fail"); + executeResultVOS.forEach(executeResultVO -> { + createRequest.setSqlType(executeResultVO.getSqlType()); + createRequest.setDuration(executeResultVO.getDuration()); + createRequest.setTableName(executeResultVO.getTableName()); + gatewayClientService.addOperationLog(createRequest); + }); + } + + +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java index 967ee086a..a76ed6071 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java @@ -1,9 +1,12 @@ package ai.chat2db.spi; import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.OrderBy; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Table; +import java.util.List; + public interface SqlBuilder { /** @@ -69,4 +72,12 @@ public interface SqlBuilder { * @return */ String buildModifySchemaSql(String oldSchemaName, String newSchemaName); + + /** + * + * @param originSql + * @param orderByList + * @return + */ + String buildOrderBySql(String originSql, List orderByList); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java index dd957f729..dc4627c32 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java @@ -2,8 +2,18 @@ import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.OrderBy; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Table; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.OrderByElement; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; public class DefaultSqlBuilder implements SqlBuilder { @@ -44,4 +54,34 @@ public String buildCreateSchemaSql(Schema schema) { public String buildModifySchemaSql(String oldSchemaName, String newSchemaName) { return null; } + + @Override + public String buildOrderBySql(String originSql, List orderByList) { + if(CollectionUtils.isEmpty(orderByList)){ + return originSql; + } + try { + Statement statement = CCJSqlParserUtil.parse(originSql); + if (statement instanceof Select) { + Select selectStatement = (Select) statement; + PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody(); + + // 创建新的 ORDER BY 子句 + List orderByElements = new ArrayList<>(); + + for (OrderBy orderBy : orderByList) { + OrderByElement orderByElement = new OrderByElement(); + orderByElement.setExpression(CCJSqlParserUtil.parseExpression(orderBy.getColumnName())); + orderByElement.setAsc(orderBy.isAsc()); // 设置为升序,使用 setAsc(false) 设置为降序 + orderByElements.add(orderByElement); + } + // 替换原有的 ORDER BY 子句 + plainSelect.setOrderByElements(orderByElements); + // 输出修改后的 SQL + return plainSelect.toString(); + } + } catch (Exception e) { + } + return originSql; + } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/OrderBy.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/OrderBy.java new file mode 100644 index 000000000..fc90c4dad --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/OrderBy.java @@ -0,0 +1,17 @@ +package ai.chat2db.spi.model; + +import lombok.Data; + +@Data +public class OrderBy { + + /** + * 排序字段 + */ + private String columnName; + + /** + * 排序方式 + */ + private boolean asc; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java index 56a28024b..9e6fce81a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java @@ -159,4 +159,13 @@ public static void removeContext() { } } + /** + * 设置context + */ + public static void remove() { + ConnectInfo connectInfo = CONNECT_INFO_THREAD_LOCAL.get(); + if (connectInfo != null) { + CONNECT_INFO_THREAD_LOCAL.remove(); + } + } } diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 01b56ab98..16c693477 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -313,7 +313,11 @@ bson 4.11.1 - + + com.github.vertical-blank + sql-formatter + 2.0.4 +