Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
UI Config Module - UI配置模块
|
||||
用于管理前端UI偏好配置
|
||||
"""
|
||||
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
UI Config API - UI配置管理接口
|
||||
提供UI配置的 CRUD 操作
|
||||
"""
|
||||
import json
|
||||
from typing import Optional, Any, Dict
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.config import settings
|
||||
from app.base_schema import PaginatedResponse, ResponseModel
|
||||
from core.ui_config.model import UIConfig
|
||||
from core.ui_config.schema import (
|
||||
UIConfigCreate, UIConfigUpdate, UIConfigResponse, UIConfigSimple,
|
||||
UIConfigValueUpdate, PreferencesConfigResponse
|
||||
)
|
||||
from core.ui_config.service import UIConfigService
|
||||
|
||||
router = APIRouter(prefix="/ui_config", tags=["UI配置管理"])
|
||||
|
||||
|
||||
@router.get("/preferences", summary="获取前端偏好配置")
|
||||
async def get_preferences(
|
||||
application_id: Optional[str] = Query(default=None, alias="applicationId", description="应用ID,不传则获取主应用配置"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取前端偏好设置配置
|
||||
返回格式与前端 Preferences 类型一致
|
||||
此接口无需认证,用于前端初始化时加载配置
|
||||
|
||||
- 不传 applicationId:获取主应用配置
|
||||
- 传 applicationId:获取子应用配置,如果子应用没有配置则回退到主应用配置
|
||||
"""
|
||||
config = await UIConfigService.get_preferences_config(db, application_id)
|
||||
return config or {}
|
||||
|
||||
|
||||
@router.put("/preferences", response_model=ResponseModel, summary="更新前端偏好配置")
|
||||
async def update_preferences(
|
||||
data: Dict[str, Any],
|
||||
application_id: Optional[str] = Query(default=None, alias="applicationId", description="应用ID,不传则更新主应用配置"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
更新前端偏好设置配置
|
||||
|
||||
- 不传 applicationId:更新主应用配置
|
||||
- 传 applicationId:更新子应用配置
|
||||
"""
|
||||
config = await UIConfigService.update_preferences_config(db, data, application_id)
|
||||
return ResponseModel(message="更新成功", data={"id": config.id})
|
||||
|
||||
|
||||
@router.post("", response_model=UIConfigResponse, summary="创建UI配置")
|
||||
async def create_ui_config(data: UIConfigCreate, db: AsyncSession = Depends(get_db)):
|
||||
"""创建UI配置"""
|
||||
# 唯一性校验
|
||||
if not await UIConfigService.check_unique(db, field="config_key", value=data.config_key):
|
||||
raise HTTPException(status_code=400, detail=f"配置键已存在: {data.config_key}")
|
||||
|
||||
config = await UIConfigService.create(db=db, data=data)
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/all", response_model=list[UIConfigSimple], summary="获取所有UI配置(简化版)")
|
||||
async def get_all_ui_configs(db: AsyncSession = Depends(get_db)):
|
||||
"""获取所有启用的UI配置(用于选择器)"""
|
||||
configs = await UIConfigService.get_all_active(db)
|
||||
return configs
|
||||
|
||||
|
||||
@router.get("", response_model=PaginatedResponse[UIConfigResponse], summary="获取UI配置列表")
|
||||
async def get_ui_config_list(
|
||||
page: int = Query(default=1, ge=1, description="页码"),
|
||||
page_size: int = Query(default=settings.PAGE_SIZE, ge=1, le=settings.PAGE_MAX_SIZE, alias="pageSize", description="每页数量"),
|
||||
config_key: Optional[str] = Query(default=None, alias="configKey", description="配置键"),
|
||||
config_type: Optional[str] = Query(default=None, alias="configType", description="配置类型"),
|
||||
status: Optional[bool] = Query(default=None, description="状态"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""获取UI配置列表(分页)"""
|
||||
filters = []
|
||||
if config_key:
|
||||
filters.append(UIConfig.config_key.ilike(f"%{config_key}%"))
|
||||
if config_type:
|
||||
filters.append(UIConfig.config_type == config_type)
|
||||
if status is not None:
|
||||
filters.append(UIConfig.status == status)
|
||||
|
||||
items, total = await UIConfigService.get_list(db, page=page, page_size=page_size, filters=filters)
|
||||
return PaginatedResponse(items=items, total=total)
|
||||
|
||||
|
||||
@router.get("/by/type/{config_type}", response_model=list[UIConfigResponse], summary="根据类型获取UI配置")
|
||||
async def get_ui_configs_by_type(config_type: str, db: AsyncSession = Depends(get_db)):
|
||||
"""根据配置类型获取UI配置列表"""
|
||||
configs = await UIConfigService.get_by_type(db, config_type)
|
||||
return configs
|
||||
|
||||
|
||||
@router.get("/by/key/{config_key}", response_model=UIConfigResponse, summary="根据配置键获取UI配置")
|
||||
async def get_ui_config_by_key(config_key: str, db: AsyncSession = Depends(get_db)):
|
||||
"""根据配置键获取UI配置"""
|
||||
config = await UIConfigService.get_by_key(db, config_key)
|
||||
if config is None:
|
||||
raise HTTPException(status_code=404, detail=f"配置键不存在: {config_key}")
|
||||
return config
|
||||
|
||||
|
||||
@router.get("/value/{config_key}", response_model=ResponseModel, summary="获取配置值")
|
||||
async def get_ui_config_value(config_key: str, db: AsyncSession = Depends(get_db)):
|
||||
"""获取配置值(解析JSON后返回)"""
|
||||
value = await UIConfigService.get_config_value(db, config_key)
|
||||
return ResponseModel(data=value)
|
||||
|
||||
|
||||
@router.put("/value/{config_key}", response_model=ResponseModel, summary="更新配置值")
|
||||
async def update_ui_config_value(
|
||||
config_key: str,
|
||||
data: UIConfigValueUpdate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""根据配置键更新配置值"""
|
||||
config = await UIConfigService.update_value_by_key(db, config_key, data.config_value)
|
||||
if config is None:
|
||||
raise HTTPException(status_code=404, detail=f"配置键不存在: {config_key}")
|
||||
return ResponseModel(message="更新成功")
|
||||
|
||||
|
||||
@router.get("/check/unique", response_model=ResponseModel, summary="检查UI配置唯一性")
|
||||
async def check_ui_config_unique(
|
||||
field: str = Query(..., description="字段名"),
|
||||
value: str = Query(..., description="字段值"),
|
||||
exclude_id: str = Query(default=None, alias="excludeId", description="排除ID"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""检查UI配置字段唯一性"""
|
||||
allowed_fields = ["config_key"]
|
||||
if field not in allowed_fields:
|
||||
raise HTTPException(status_code=400, detail=f"不支持检查字段: {field}")
|
||||
|
||||
is_unique = await UIConfigService.check_unique(db, field=field, value=value, exclude_id=exclude_id)
|
||||
return ResponseModel(message="可用" if is_unique else "已存在", data={"unique": is_unique})
|
||||
|
||||
|
||||
@router.get("/{config_id}", response_model=UIConfigResponse, summary="获取UI配置详情")
|
||||
async def get_ui_config_by_id(config_id: str, db: AsyncSession = Depends(get_db)):
|
||||
"""获取UI配置详情"""
|
||||
config = await UIConfigService.get_by_id(db, config_id)
|
||||
if config is None:
|
||||
raise HTTPException(status_code=404, detail="UI配置不存在")
|
||||
return config
|
||||
|
||||
|
||||
@router.put("/{config_id}", response_model=UIConfigResponse, summary="更新UI配置")
|
||||
async def update_ui_config(config_id: str, data: UIConfigUpdate, db: AsyncSession = Depends(get_db)):
|
||||
"""更新UI配置"""
|
||||
# 唯一性校验(排除自身)
|
||||
if data.config_key and not await UIConfigService.check_unique(db, field="config_key", value=data.config_key, exclude_id=config_id):
|
||||
raise HTTPException(status_code=400, detail=f"配置键已存在: {data.config_key}")
|
||||
|
||||
config = await UIConfigService.update(db, record_id=config_id, data=data)
|
||||
if config is None:
|
||||
raise HTTPException(status_code=404, detail="UI配置不存在")
|
||||
return config
|
||||
|
||||
|
||||
@router.delete("/{config_id}", response_model=ResponseModel, summary="删除UI配置")
|
||||
async def delete_ui_config(
|
||||
config_id: str,
|
||||
hard: bool = Query(default=False, description="是否物理删除"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""删除UI配置"""
|
||||
success = await UIConfigService.delete(db, record_id=config_id, hard=hard)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="UI配置不存在")
|
||||
return ResponseModel(message="删除成功")
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
UI Config Model - UI配置模型
|
||||
用于存储前端UI偏好配置
|
||||
"""
|
||||
from sqlalchemy import Column, String, Text, Boolean
|
||||
|
||||
from app.base_model import BaseModel
|
||||
|
||||
|
||||
class UIConfig(BaseModel):
|
||||
"""
|
||||
UI配置表
|
||||
|
||||
字段说明:
|
||||
- application_id: 所属应用ID(空为主应用配置)
|
||||
- config_key: 配置键(同一应用内唯一)
|
||||
- config_value: 配置值(JSON格式)
|
||||
- config_type: 配置类型(preferences/theme/logo等)
|
||||
- description: 配置描述
|
||||
- status: 状态
|
||||
"""
|
||||
__tablename__ = "core_ui_config"
|
||||
|
||||
# 所属应用(逻辑外键关联 core_application,空为主应用配置)
|
||||
application_id = Column(String(21), nullable=True, index=True, comment="所属应用ID")
|
||||
|
||||
# 配置键(同一应用内唯一,通过代码逻辑保证)
|
||||
config_key = Column(String(100), nullable=False, index=True, comment="配置键")
|
||||
|
||||
# 配置值(JSON格式)
|
||||
config_value = Column(Text, nullable=True, comment="配置值(JSON)")
|
||||
|
||||
# 配置类型
|
||||
config_type = Column(String(50), nullable=False, default="preferences", index=True, comment="配置类型")
|
||||
|
||||
# 配置描述
|
||||
description = Column(String(200), nullable=True, comment="配置描述")
|
||||
|
||||
# 状态
|
||||
status = Column(Boolean, default=True, index=True, comment="状态")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.config_key} ({self.config_type})"
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
UI Config Schema - UI配置数据验证模式
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any, Dict
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from app.base_schema import CSTDatetime
|
||||
|
||||
|
||||
class UIConfigBase(BaseModel):
|
||||
"""UI配置基础Schema"""
|
||||
application_id: Optional[str] = Field(default=None, description="所属应用ID(空为主应用配置)")
|
||||
config_key: str = Field(..., min_length=1, max_length=100, description="配置键")
|
||||
config_value: Optional[str] = Field(None, description="配置值(JSON)")
|
||||
config_type: str = Field(default="preferences", max_length=50, description="配置类型")
|
||||
description: Optional[str] = Field(None, max_length=200, description="配置描述")
|
||||
status: bool = Field(default=True, description="状态")
|
||||
sort: int = Field(default=0, description="排序")
|
||||
|
||||
@field_validator("config_key")
|
||||
@classmethod
|
||||
def validate_config_key(cls, v):
|
||||
"""验证配置键格式"""
|
||||
if not v:
|
||||
raise ValueError("配置键不能为空")
|
||||
if not v.replace('_', '').replace('-', '').isalnum():
|
||||
raise ValueError("配置键只能包含字母、数字、下划线和短横线")
|
||||
return v
|
||||
|
||||
|
||||
class UIConfigCreate(UIConfigBase):
|
||||
"""UI配置创建Schema"""
|
||||
pass
|
||||
|
||||
|
||||
class UIConfigUpdate(BaseModel):
|
||||
"""UI配置更新Schema - 所有字段可选"""
|
||||
application_id: Optional[str] = Field(default=None, description="所属应用ID")
|
||||
config_key: Optional[str] = Field(None, min_length=1, max_length=100, description="配置键")
|
||||
config_value: Optional[str] = Field(None, description="配置值(JSON)")
|
||||
config_type: Optional[str] = Field(None, max_length=50, description="配置类型")
|
||||
description: Optional[str] = Field(None, max_length=200, description="配置描述")
|
||||
status: Optional[bool] = Field(None, description="状态")
|
||||
sort: Optional[int] = Field(None, description="排序")
|
||||
|
||||
@field_validator("config_key")
|
||||
@classmethod
|
||||
def validate_config_key(cls, v):
|
||||
"""验证配置键格式"""
|
||||
if v is not None:
|
||||
if not v:
|
||||
raise ValueError("配置键不能为空")
|
||||
if not v.replace('_', '').replace('-', '').isalnum():
|
||||
raise ValueError("配置键只能包含字母、数字、下划线和短横线")
|
||||
return v
|
||||
|
||||
|
||||
class UIConfigResponse(BaseModel):
|
||||
"""UI配置响应Schema"""
|
||||
id: str
|
||||
application_id: Optional[str] = None
|
||||
config_key: str
|
||||
config_value: Optional[str] = None
|
||||
config_type: str
|
||||
description: Optional[str] = None
|
||||
status: bool
|
||||
sort: int = 0
|
||||
is_deleted: bool = False
|
||||
sys_create_datetime: Optional[CSTDatetime] = None
|
||||
sys_update_datetime: Optional[CSTDatetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class UIConfigSimple(BaseModel):
|
||||
"""UI配置简单输出(用于选择器)"""
|
||||
id: str
|
||||
config_key: str
|
||||
config_type: str
|
||||
status: bool
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class UIConfigValueUpdate(BaseModel):
|
||||
"""UI配置值更新Schema(仅更新值)"""
|
||||
config_value: str = Field(..., description="配置值(JSON)")
|
||||
|
||||
|
||||
class PreferencesConfigResponse(BaseModel):
|
||||
"""前端偏好配置响应Schema"""
|
||||
app: Optional[Dict[str, Any]] = None
|
||||
theme: Optional[Dict[str, Any]] = None
|
||||
logo: Optional[Dict[str, Any]] = None
|
||||
copyright: Optional[Dict[str, Any]] = None
|
||||
sidebar: Optional[Dict[str, Any]] = None
|
||||
header: Optional[Dict[str, Any]] = None
|
||||
footer: Optional[Dict[str, Any]] = None
|
||||
tabbar: Optional[Dict[str, Any]] = None
|
||||
breadcrumb: Optional[Dict[str, Any]] = None
|
||||
navigation: Optional[Dict[str, Any]] = None
|
||||
shortcutKeys: Optional[Dict[str, Any]] = None
|
||||
transition: Optional[Dict[str, Any]] = None
|
||||
widget: Optional[Dict[str, Any]] = None
|
||||
loginConfig: Optional[Dict[str, Any]] = None
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
UI Config Service - UI配置服务层
|
||||
"""
|
||||
import json
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.base_service import BaseService
|
||||
from core.ui_config.model import UIConfig
|
||||
from core.ui_config.schema import UIConfigCreate, UIConfigUpdate
|
||||
|
||||
|
||||
class UIConfigService(BaseService[UIConfig, UIConfigCreate, UIConfigUpdate]):
|
||||
"""
|
||||
UI配置服务层
|
||||
继承BaseService,自动获得增删改查功能
|
||||
"""
|
||||
|
||||
model = UIConfig
|
||||
|
||||
@classmethod
|
||||
async def get_by_key(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
config_key: str,
|
||||
application_id: Optional[str] = None
|
||||
) -> Optional[UIConfig]:
|
||||
"""
|
||||
根据配置键获取配置
|
||||
|
||||
:param db: 数据库会话
|
||||
:param config_key: 配置键
|
||||
:param application_id: 应用ID,None表示主应用配置
|
||||
"""
|
||||
query = select(UIConfig).where(
|
||||
UIConfig.config_key == config_key,
|
||||
UIConfig.is_deleted == False # noqa: E712
|
||||
)
|
||||
if application_id:
|
||||
query = query.where(UIConfig.application_id == application_id)
|
||||
else:
|
||||
query = query.where(UIConfig.application_id.is_(None))
|
||||
result = await db.execute(query)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
@classmethod
|
||||
async def get_by_type(cls, db: AsyncSession, config_type: str) -> List[UIConfig]:
|
||||
"""根据配置类型获取配置列表"""
|
||||
result = await db.execute(
|
||||
select(UIConfig).where(
|
||||
UIConfig.config_type == config_type,
|
||||
UIConfig.status == True, # noqa: E712
|
||||
UIConfig.is_deleted == False # noqa: E712
|
||||
).order_by(UIConfig.sort)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def get_all_active(cls, db: AsyncSession) -> List[UIConfig]:
|
||||
"""获取所有启用的配置"""
|
||||
result = await db.execute(
|
||||
select(UIConfig).where(
|
||||
UIConfig.status == True, # noqa: E712
|
||||
UIConfig.is_deleted == False # noqa: E712
|
||||
).order_by(UIConfig.sort)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def get_preferences_config(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
application_id: Optional[str] = None
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
获取前端偏好配置
|
||||
返回格式与前端 Preferences 类型一致
|
||||
|
||||
:param db: 数据库会话
|
||||
:param application_id: 应用ID,None表示主应用配置
|
||||
"""
|
||||
config = await cls.get_by_key(db, "frontend_preferences", application_id)
|
||||
|
||||
if config and config.config_value and config.status:
|
||||
try:
|
||||
return json.loads(config.config_value)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
# 如果子应用没有配置,回退到主应用配置
|
||||
if application_id:
|
||||
return await cls.get_preferences_config(db, None)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def update_preferences_config(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
preferences: Dict[str, Any],
|
||||
application_id: Optional[str] = None
|
||||
) -> UIConfig:
|
||||
"""
|
||||
更新前端偏好配置
|
||||
如果不存在则创建
|
||||
|
||||
:param db: 数据库会话
|
||||
:param preferences: 偏好配置
|
||||
:param application_id: 应用ID,None表示主应用配置
|
||||
"""
|
||||
config = await cls.get_by_key(db, "frontend_preferences", application_id)
|
||||
config_value = json.dumps(preferences, ensure_ascii=False)
|
||||
|
||||
if config:
|
||||
config.config_value = config_value
|
||||
await db.commit()
|
||||
await db.refresh(config)
|
||||
return config
|
||||
else:
|
||||
new_config = UIConfig(
|
||||
application_id=application_id,
|
||||
config_key="frontend_preferences",
|
||||
config_value=config_value,
|
||||
config_type="preferences",
|
||||
description="前端UI偏好配置" if not application_id else f"子应用UI偏好配置",
|
||||
status=True
|
||||
)
|
||||
db.add(new_config)
|
||||
await db.commit()
|
||||
await db.refresh(new_config)
|
||||
return new_config
|
||||
|
||||
@classmethod
|
||||
async def update_value_by_key(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
config_key: str,
|
||||
config_value: str
|
||||
) -> Optional[UIConfig]:
|
||||
"""根据配置键更新配置值"""
|
||||
config = await cls.get_by_key(db, config_key)
|
||||
if config:
|
||||
config.config_value = config_value
|
||||
await db.commit()
|
||||
await db.refresh(config)
|
||||
return config
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def get_config_value(cls, db: AsyncSession, config_key: str) -> Optional[Any]:
|
||||
"""获取配置值(解析JSON)"""
|
||||
config = await cls.get_by_key(db, config_key)
|
||||
if config and config.config_value:
|
||||
try:
|
||||
return json.loads(config.config_value)
|
||||
except json.JSONDecodeError:
|
||||
return config.config_value
|
||||
return None
|
||||
Reference in New Issue
Block a user