perf: Terminology module

This commit is contained in:
fit2cloud-chenyw
2025-04-27 18:46:53 +08:00
parent becde51f06
commit 0796fde819
13 changed files with 355 additions and 34 deletions
@@ -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 ###
+49 -2
View File
@@ -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
+33
View File
@@ -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
}
+57
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
alembic upgrade head
Regular → Executable
View File
Regular → Executable
View File
+1
View File
@@ -7,4 +7,5 @@
export {}
declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
}
+9
View File
@@ -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}`)
}
+3 -4
View File
@@ -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 }
+163 -23
View File
@@ -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>