mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
perf: Terminology module
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"""003_Upgrade_Term_Table
|
||||
|
||||
Revision ID: dd9d72cfd3c4
|
||||
Revises: 1c8bcc7e25c8
|
||||
Create Date: 2025-04-27 14:56:29.185812
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'dd9d72cfd3c4'
|
||||
down_revision = '1c8bcc7e25c8'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('terms', 'id',
|
||||
existing_type=sa.INTEGER(),
|
||||
type_=sa.BigInteger(),
|
||||
existing_nullable=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('terms', 'id',
|
||||
existing_type=sa.BigInteger(),
|
||||
type_=sa.INTEGER(),
|
||||
existing_nullable=False)
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,5 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter
|
||||
from sqlmodel import select
|
||||
from apps.settings.models.setting_models import term_model
|
||||
from apps.settings.schemas.setting_schemas import term_schema_creator
|
||||
from common.core.deps import SessionDep
|
||||
from common.core.pagination import Paginator
|
||||
from common.core.schemas import PaginatedResponse, PaginationParams
|
||||
@@ -18,4 +20,49 @@ async def pager(
|
||||
return await paginator.get_paginated_response(
|
||||
model=term_model,
|
||||
pagination=pagination,
|
||||
**filters)
|
||||
**filters)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=term_model)
|
||||
async def get_terminology_by_id(
|
||||
session: SessionDep,
|
||||
id: int
|
||||
):
|
||||
term = session.get(term_model, id)
|
||||
return term
|
||||
|
||||
@router.post("", response_model=term_model)
|
||||
async def add_terminology(
|
||||
session: SessionDep,
|
||||
creator: term_schema_creator
|
||||
):
|
||||
terminology = term_model(**creator.model_dump())
|
||||
session.add(terminology)
|
||||
session.commit()
|
||||
return terminology
|
||||
|
||||
@router.put("", response_model=term_model)
|
||||
async def update_terminology(
|
||||
session: SessionDep,
|
||||
terminology: term_model
|
||||
):
|
||||
terminology.id = int(terminology.id)
|
||||
term = session.exec(select(term_model).where(term_model.id == terminology.id)).first()
|
||||
update_data = terminology.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(term, field, value)
|
||||
session.add(term)
|
||||
session.commit()
|
||||
return terminology
|
||||
|
||||
@router.delete("/{id}", response_model=term_model)
|
||||
async def delete_terminology(
|
||||
session: SessionDep,
|
||||
id: int
|
||||
):
|
||||
term = session.exec(select(term_model).where(term_model.id == id)).first()
|
||||
session.delete(term)
|
||||
session.commit()
|
||||
return {
|
||||
"message": f"Terminology with ID {id} deleted successfully."
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
from common.core.models import SnowflakeBase
|
||||
|
||||
class term_model(SQLModel, table=True):
|
||||
|
||||
class term_model(SnowflakeBase, table=True):
|
||||
__tablename__ = "terms"
|
||||
|
||||
id: int = Field(primary_key=True, index=True)
|
||||
term: str = Field(max_length=255)
|
||||
definition: str = Field(max_length=255)
|
||||
domain: str = Field(max_length=255)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class term_schema(BaseModel):
|
||||
id: int
|
||||
class term_schema_creator(BaseModel):
|
||||
term: str
|
||||
definition: str
|
||||
domain: str
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
from pydantic import field_validator
|
||||
from sqlmodel import BigInteger, SQLModel, Field
|
||||
from typing import Optional
|
||||
|
||||
from common.utils.snowflake import snowflake
|
||||
|
||||
class SnowflakeBase(SQLModel):
|
||||
id: Optional[int] = Field(
|
||||
default_factory=snowflake.generate_id,
|
||||
primary_key=True,
|
||||
sa_type=BigInteger(),
|
||||
index=True,
|
||||
nullable=False
|
||||
)
|
||||
|
||||
@field_validator("id", mode="before")
|
||||
def validate_id(cls, v):
|
||||
if isinstance(v, str):
|
||||
if not v:
|
||||
return None
|
||||
try:
|
||||
return int(v)
|
||||
except ValueError:
|
||||
raise ValueError("Invalid bigint string")
|
||||
elif isinstance(v, int):
|
||||
return v
|
||||
raise TypeError("BigInt must be int or string")
|
||||
|
||||
|
||||
class Config:
|
||||
json_encoders = {
|
||||
int: lambda v: str(v) if v > 2**53-1 else v
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import time
|
||||
import threading
|
||||
class SnowflakeGenerator:
|
||||
def __init__(self, worker_id=0, datacenter_id=0):
|
||||
self.worker_id = worker_id
|
||||
self.datacenter_id = datacenter_id
|
||||
self.sequence = 0
|
||||
self.last_timestamp = -1
|
||||
self.lock = threading.Lock()
|
||||
|
||||
self.worker_id_bits = 5
|
||||
self.datacenter_id_bits = 5
|
||||
self.max_worker_id = -1 ^ (-1 << self.worker_id_bits)
|
||||
self.max_datacenter_id = -1 ^ (-1 << self.datacenter_id_bits)
|
||||
self.sequence_bits = 12
|
||||
|
||||
self.worker_id_shift = self.sequence_bits
|
||||
self.datacenter_id_shift = self.sequence_bits + self.worker_id_bits
|
||||
self.timestamp_left_shift = self.sequence_bits + self.worker_id_bits + self.datacenter_id_bits
|
||||
self.sequence_mask = -1 ^ (-1 << self.sequence_bits)
|
||||
|
||||
if self.worker_id > self.max_worker_id or self.worker_id < 0:
|
||||
raise ValueError(f"worker ID can't be greater than {self.max_worker_id} or less than 0")
|
||||
if self.datacenter_id > self.max_datacenter_id or self.datacenter_id < 0:
|
||||
raise ValueError(f"datacenter ID can't be greater than {self.max_datacenter_id} or less than 0")
|
||||
|
||||
def _current_time(self):
|
||||
return int(time.time() * 1000)
|
||||
|
||||
def _wait_next_millis(self, last_timestamp):
|
||||
timestamp = self._current_time()
|
||||
while timestamp <= last_timestamp:
|
||||
timestamp = self._current_time()
|
||||
return timestamp
|
||||
|
||||
def generate_id(self):
|
||||
with self.lock:
|
||||
timestamp = self._current_time()
|
||||
|
||||
if timestamp < self.last_timestamp:
|
||||
raise ValueError("Clock moved backwards. Refusing to generate ID")
|
||||
|
||||
if timestamp == self.last_timestamp:
|
||||
self.sequence = (self.sequence + 1) & self.sequence_mask
|
||||
if self.sequence == 0:
|
||||
timestamp = self._wait_next_millis(self.last_timestamp)
|
||||
else:
|
||||
self.sequence = 0
|
||||
|
||||
self.last_timestamp = timestamp
|
||||
|
||||
return ((timestamp << self.timestamp_left_shift) |
|
||||
(self.datacenter_id << self.datacenter_id_shift) |
|
||||
(self.worker_id << self.worker_id_shift) |
|
||||
self.sequence)
|
||||
|
||||
snowflake = SnowflakeGenerator(worker_id=1)
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
alembic upgrade head
|
||||
Regular → Executable
Regular → Executable
Vendored
+1
@@ -7,4 +7,5 @@
|
||||
export {}
|
||||
declare global {
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export const termApi = {
|
||||
pager: (pageNumber: number, pageSize: number) => request.get(`/settings/terminology/pager/${pageNumber}/${pageSize}`),
|
||||
add: (data: any) => request.post('/settings/terminology', data),
|
||||
edit: (data: any) => request.put('/settings/terminology', data),
|
||||
delete: (id: number) => request.delete(`/settings/terminology/${id}`),
|
||||
query: (id: number) => request.get(`/settings/terminology/${id}`)
|
||||
}
|
||||
@@ -91,11 +91,10 @@ class HttpService {
|
||||
}
|
||||
|
||||
// Handle business logic
|
||||
if (response.data?.success !== true) {
|
||||
/* if (response.data?.success !== true) {
|
||||
return Promise.reject(response.data)
|
||||
}
|
||||
|
||||
return response.data.data
|
||||
} */
|
||||
return response.data
|
||||
},
|
||||
async (error: AxiosError) => {
|
||||
const config = error.config as FullRequestConfig & { __retryCount?: number }
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<el-icon><IconOpeDownload /></el-icon>
|
||||
<span>Export Terminology</span>
|
||||
</div>
|
||||
<div class="tool-btn primary-btn">
|
||||
<div class="tool-btn primary-btn" @click="openDialog">
|
||||
<el-icon><IconOpeAdd /></el-icon>
|
||||
<span>Add Terminology</span>
|
||||
</div>
|
||||
@@ -49,48 +49,154 @@
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" title="Add Terminology" destroy-on-close width="500" @close="onFormClose" class="sqlbot-dialog">
|
||||
<el-form :model="state.form"
|
||||
label-width="180px"
|
||||
label-position="top"
|
||||
class="sqlbot-form"
|
||||
size="large"
|
||||
ref="termFormRef"
|
||||
>
|
||||
<el-form-item label="Term name" >
|
||||
<el-input v-model="state.form.term" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Definition" >
|
||||
<el-input type="textarea" v-model="state.form.definition" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Domain">
|
||||
<el-select v-model="state.form.domain" placeholder="Please select a domain">
|
||||
<el-option label="domain1" value="domain1" />
|
||||
<el-option label="domain2" value="domain2" />
|
||||
<el-option label="domain3" value="domain3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeForm">Cancel</el-button>
|
||||
<el-button type="primary" @click="saveHandler">
|
||||
save
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import IconOpeUpload from '@/assets/svg/operate/ope-upload.svg';
|
||||
import IconOpeDownload from '@/assets/svg/operate/ope-download.svg';
|
||||
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg';
|
||||
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg';
|
||||
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg';
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { termApi } from '@/api/setting'
|
||||
const keyword = ref('')
|
||||
const dialogFormVisible = ref(false)
|
||||
const termFormRef = ref()
|
||||
const state = reactive({
|
||||
tableData: [
|
||||
{
|
||||
term: 'Term 1',
|
||||
definition: 'Definition 1',
|
||||
domain: 'Domain 1',
|
||||
id: '1'
|
||||
},
|
||||
{
|
||||
term: 'Term 2',
|
||||
definition: 'Definition 2',
|
||||
domain: 'Domain 2',
|
||||
id: '2'
|
||||
},
|
||||
{
|
||||
term: 'Term 3',
|
||||
definition: 'Definition 3',
|
||||
domain: 'Domain 3',
|
||||
id: '3'
|
||||
}
|
||||
]
|
||||
],
|
||||
form: {
|
||||
id: '',
|
||||
term: '',
|
||||
definition: '',
|
||||
domain: ''
|
||||
},
|
||||
})
|
||||
const handleSearch = (e: any) => {
|
||||
console.log('search', e);
|
||||
}
|
||||
const editHandler = (id: any) => {
|
||||
console.log(id)
|
||||
termApi.query(id).then((res: any) => {
|
||||
console.log('term detail', res)
|
||||
state.form = res
|
||||
dialogFormVisible.value = true
|
||||
})
|
||||
}
|
||||
const deleteHandler = (id: any) => {
|
||||
console.log(id)
|
||||
ElMessageBox.confirm(
|
||||
'Are you sure to delete?',
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
termApi.delete(id).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Delete completed',
|
||||
})
|
||||
search()
|
||||
})
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: 'Delete canceled',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDialog = () => {
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
const resetForm = () => {
|
||||
if (!termFormRef) return
|
||||
Object.keys(state.form).forEach((key) => {
|
||||
state.form[key as keyof typeof state.form] = ''
|
||||
})
|
||||
}
|
||||
|
||||
const closeForm = () => {
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
const onFormClose = () => {
|
||||
resetForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
termApi.pager(1, 20).then((res: any) => {
|
||||
state.tableData = res.items
|
||||
})
|
||||
}
|
||||
const addTerm = () => {
|
||||
termApi.add(state.form).then((res: any) => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Add completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
const editTerm = () => {
|
||||
termApi.edit(state.form).then((res: any) => {
|
||||
console.log('edit term', res)
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Edit completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
const saveHandler = () => {
|
||||
if (state.form.id) {
|
||||
editTerm()
|
||||
} else {
|
||||
addTerm()
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
search()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -195,4 +301,38 @@ const deleteHandler = (id: any) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-form {
|
||||
width: 100%;
|
||||
.el-form-item {
|
||||
margin-bottom: 16px;
|
||||
text-align: left;
|
||||
label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
/* .el-input {
|
||||
--el-input-border-color: #e5e8ed;
|
||||
--el-input-border-radius: 4px;
|
||||
--el-input-inner-height: 40px !important;
|
||||
}
|
||||
.el-select {
|
||||
--el-select-bg-color: #f5f7fa;
|
||||
--el-select-border-color: #e5e8ed;
|
||||
--el-select-border-radius: 4px;
|
||||
--el-select-inner-height: 40px !important;
|
||||
} */
|
||||
}
|
||||
}
|
||||
.sqlbot-dialog {
|
||||
width: 500px;
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
text-align: left !important;
|
||||
font-size: 16px;
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
color: #606266;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user