Skip to content

Commit

Permalink
Add page to display dynamic configuration (#365)
Browse files Browse the repository at this point in the history
* Add page to display dynamic configuration

* update config data and api

* add config route

* update config api route

* update config api route

* fix config schema

* update config services
  • Loading branch information
wu-clan authored Jul 25, 2024
1 parent f94c2d7 commit 34506c3
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 6 deletions.
2 changes: 2 additions & 0 deletions backend/app/admin/api/v1/sys/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from backend.app.admin.api.v1.sys.api import router as api_router
from backend.app.admin.api.v1.sys.casbin import router as casbin_router
from backend.app.admin.api.v1.sys.config import router as config_router
from backend.app.admin.api.v1.sys.dept import router as dept_router
from backend.app.admin.api.v1.sys.dict_data import router as dict_data_router
from backend.app.admin.api.v1.sys.dict_type import router as dict_type_router
Expand All @@ -15,6 +16,7 @@

router.include_router(api_router, prefix='/apis', tags=['系统API'])
router.include_router(casbin_router, prefix='/casbin', tags=['系统Casbin权限'])
router.include_router(config_router, prefix='/configs', tags=['系统配置'])
router.include_router(dept_router, prefix='/depts', tags=['系统部门'])
router.include_router(dict_data_router, prefix='/dict_datas', tags=['系统字典数据'])
router.include_router(dict_type_router, prefix='/dict_types', tags=['系统字典类型'])
Expand Down
63 changes: 63 additions & 0 deletions backend/app/admin/api/v1/sys/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Annotated

from fastapi import APIRouter, Depends, Path, Query

from backend.app.admin.schema.config import CreateConfigParam, UpdateConfigParam
from backend.app.admin.service.config_service import config_service
from backend.common.response.response_schema import ResponseModel, response_base
from backend.common.security.jwt import DependsJwtAuth
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC

router = APIRouter()


@router.get('', summary='获取系统配置详情', dependencies=[DependsJwtAuth])
async def get_config() -> ResponseModel:
config = await config_service.get()
return await response_base.success(data=config)


@router.post(
'',
summary='创建系统配置',
dependencies=[
Depends(RequestPermission('sys:config:add')),
DependsRBAC,
],
)
async def create_config(obj: CreateConfigParam) -> ResponseModel:
await config_service.create(obj=obj)
return await response_base.success()


@router.put(
'/{pk}',
summary='更新系统配置',
dependencies=[
Depends(RequestPermission('sys:config:edit')),
DependsRBAC,
],
)
async def update_config(pk: Annotated[int, Path(...)], obj: UpdateConfigParam) -> ResponseModel:
count = await config_service.update(pk=pk, obj=obj)
if count > 0:
return await response_base.success()
return await response_base.fail()


@router.delete(
'',
summary='(批量)删除系统配置',
dependencies=[
Depends(RequestPermission('sys:config:del')),
DependsRBAC,
],
)
async def delete_config(pk: Annotated[list[int], Query(...)]) -> ResponseModel:
count = await config_service.delete(pk=pk)
if count > 0:
return await response_base.success()
return await response_base.fail()
3 changes: 3 additions & 0 deletions backend/app/admin/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class AdminSettings(BaseSettings):
CAPTCHA_LOGIN_REDIS_PREFIX: str = 'fba_login_captcha'
CAPTCHA_LOGIN_EXPIRE_SECONDS: int = 60 * 5 # 过期时间,单位:秒

# Config
CONFIG_REDIS_KEY: str = 'fba_config'


@lru_cache
def get_admin_settings() -> AdminSettings:
Expand Down
66 changes: 66 additions & 0 deletions backend/app/admin/crud/crud_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Sequence

from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus

from backend.app.admin.model import Config
from backend.app.admin.schema.config import CreateConfigParam, UpdateConfigParam


class CRUDConfig(CRUDPlus[Config]):
async def get_one(self, db: AsyncSession) -> Config | None:
"""
获取 Config
:param db:
:return:
"""
query = await db.execute(select(self.model).limit(1))
return query.scalars().first()

async def get_all(self, db: AsyncSession) -> Sequence[Config]:
"""
获取所有 Config
:param db:
:return:
"""
return await self.select_models(db)

async def create(self, db: AsyncSession, obj_in: CreateConfigParam) -> None:
"""
创建 Config
:param db:
:param obj_in:
:return:
"""
await self.create_model(db, obj_in)

async def update(self, db: AsyncSession, pk: int, obj_in: UpdateConfigParam) -> int:
"""
更新 Config
:param db:
:param pk:
:param obj_in:
:return:
"""
return await self.update_model(db, pk, obj_in)

async def delete(self, db: AsyncSession, pk: list[int]) -> int:
"""
删除 Config
:param db:
:param pk:
:return:
"""
configs = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
return configs.rowcount


config_dao: CRUDConfig = CRUDConfig(Config)
1 change: 1 addition & 0 deletions backend/app/admin/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
from backend.app.admin.model.sys_api import Api
from backend.app.admin.model.sys_casbin_rule import CasbinRule
from backend.app.admin.model.sys_config import Config
from backend.app.admin.model.sys_dept import Dept
from backend.app.admin.model.sys_dict_data import DictData
from backend.app.admin.model.sys_dict_type import DictType
Expand Down
28 changes: 28 additions & 0 deletions backend/app/admin/model/sys_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column

from backend.common.model import Base, id_key


class Config(Base):
"""系统配置表"""

__tablename__ = 'sys_config'

id: Mapped[id_key] = mapped_column(init=False)
login_title: Mapped[str] = mapped_column(String(20), default='登陆 FBA', comment='登陆页面标题')
login_sub_title: Mapped[str] = mapped_column(
String(50), default='fastapi_best_architecture', comment='登陆页面子标题'
)
footer: Mapped[str] = mapped_column(String(50), default='FBA', comment='页脚标题')
logo: Mapped[str] = mapped_column(LONGTEXT, default='Arco', comment='Logo')
system_title: Mapped[str] = mapped_column(String(20), default='Arco', comment='系统标题')
system_comment: Mapped[str] = mapped_column(
LONGTEXT,
default='基于 FastAPI 构建的前后端分离 RBAC 权限控制系统,采用独特的伪三层架构模型设计,'
'内置 fastapi-admin 基本实现,并作为模板库免费开源',
comment='系统描述',
)
35 changes: 35 additions & 0 deletions backend/app/admin/schema/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime

from pydantic import ConfigDict, Field

from backend.common.schema import SchemaBase


class ConfigSchemaBase(SchemaBase):
login_title: str = Field(default='登陆 FBA')
login_sub_title: str = Field(default='fastapi_best_architecture')
footer: str = Field(default='FBA')
logo: str = Field(default='Arco')
system_title: str = Field(default='Arco')
system_comment: str = Field(
default='基于 FastAPI 构建的前后端分离 RBAC 权限控制系统,采用独特的伪三层架构模型设计,'
'内置 fastapi-admin 基本实现,并作为模板库免费开源'
)


class CreateConfigParam(ConfigSchemaBase):
pass


class UpdateConfigParam(ConfigSchemaBase):
pass


class GetConfigListDetails(ConfigSchemaBase):
model_config = ConfigDict(from_attributes=True)

id: int
created_time: datetime
updated_time: datetime | None = None
57 changes: 57 additions & 0 deletions backend/app/admin/service/config_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from backend.app.admin.conf import admin_settings
from backend.app.admin.crud.crud_config import config_dao
from backend.app.admin.model import Config
from backend.app.admin.schema.config import CreateConfigParam, UpdateConfigParam
from backend.common.exception import errors
from backend.database.db_mysql import async_db_session
from backend.database.db_redis import redis_client
from backend.utils.serializers import select_as_dict


class ConfigService:
@staticmethod
async def get() -> Config | dict:
async with async_db_session() as db:
cache_config = await redis_client.hgetall(admin_settings.CONFIG_REDIS_KEY)
if not cache_config:
config = await config_dao.get_one(db)
if not config:
raise errors.NotFoundError(msg='系统配置不存在')
data_map = await select_as_dict(config)
del data_map['created_time']
del data_map['updated_time']
await redis_client.hset(admin_settings.CONFIG_REDIS_KEY, mapping=data_map)
return config
else:
return cache_config

@staticmethod
async def create(*, obj: CreateConfigParam) -> None:
async with async_db_session.begin() as db:
config = await config_dao.get_one(db)
if config:
raise errors.ForbiddenError(msg='系统配置已存在')
await config_dao.create(db, obj)
await redis_client.hset(admin_settings.CONFIG_REDIS_KEY, mapping=obj.model_dump())

@staticmethod
async def update(*, pk: int, obj: UpdateConfigParam) -> int:
async with async_db_session.begin() as db:
count = await config_dao.update(db, pk, obj)
await redis_client.hset(admin_settings.CONFIG_REDIS_KEY, mapping=obj.model_dump())
return count

@staticmethod
async def delete(*, pk: list[int]) -> int:
async with async_db_session.begin() as db:
configs = await config_dao.get_all(db)
if len(configs) == 1:
raise errors.ForbiddenError(msg='系统配置无法彻底删除')
count = await config_dao.delete(db, pk)
await redis_client.delete(admin_settings.CONFIG_REDIS_KEY)
return count


config_service = ConfigService()
4 changes: 2 additions & 2 deletions backend/templates/py/crud.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class CRUD{{ table_name_class }}(CRUDPlus[{{ schema_name }}]):
"""
return await self.select_model_by_id(db, pk)

async def get_all(self, db: AsyncSession) -> Sequence[{{ schema_name }}]:
async def get_all(self, db: AsyncSession) -> Sequence[{{ table_name_class }}]:
"""
获取所有 {{ schema_name }}
Expand Down Expand Up @@ -63,4 +63,4 @@ class CRUD{{ table_name_class }}(CRUDPlus[{{ schema_name }}]):
return {{ table_name_en }}s.rowcount


{{ table_name_en }}_dao: CRUD{{ schema_name }} = CRUD{{ schema_name }}({{ schema_name }})
{{ table_name_en }}_dao: CRUD{{ table_name_class }} = CRUD{{ table_name_class }}({{ table_name_class }})
6 changes: 3 additions & 3 deletions backend/templates/py/service.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ from backend.common.exception import errors
from backend.database.db_mysql import async_db_session


class {{ schema_name }}Service:
class {{ table_name_class }}Service:
@staticmethod
async def get(*, pk: int) -> {{ table_name_class }}:
async with async_db_session() as db:
{{ table_name_en }} = await {{ table_name_en }}_dao.get(db, pk)
if not {{ table_name_en }}:
raise errors.NotFoundError(msg='接口不存在')
raise errors.NotFoundError(msg='{{ table_simple_name_zh }}不存在')
return {{ table_name_en }}

@staticmethod
Expand Down Expand Up @@ -45,4 +45,4 @@ class {{ schema_name }}Service:
return count


{{ table_name_en }}_service = {{ schema_name }}Service()
{{ table_name_en }}_service = {{ table_name_class }}Service()
2 changes: 1 addition & 1 deletion backend/utils/gen_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def get_vars(business: GenBusiness, models: list[GenModel]) -> dict:
'table_comment': business.table_comment,
'schema_name': to_pascal(business.schema_name),
'have_datetime_column': business.have_datetime_column,
'permission_sign': str(business.__tablename__.replace('_', ':')),
'permission_sign': str(business.table_name_en.replace('_', ':')),
'models': models,
}

Expand Down

0 comments on commit 34506c3

Please sign in to comment.