From c6b2712a78e42d16ba8b3d110e851f55c8ca55f5 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Wed, 18 Mar 2026 09:14:08 +0800 Subject: [PATCH] sync --- .dockerignore | 1 + .github/workflows/changelog.yml | 10 +- .github/workflows/lint.yml | 4 +- .github/workflows/version.yml | 25 + .gitignore | 2 + .pre-commit-config.yaml | 12 +- .ruff.toml | 269 +-- .schemas/plugin.schema.json | 138 ++ CHANGELOG.md | 1683 +++++++------- CONTRIBUTING.md | 8 +- Dockerfile | 14 +- SLIM_GUIDE.md | 272 +++ backend/__init__.py | 2 +- backend/alembic.ini | 81 - backend/alembic/env.py | 22 +- backend/app/admin/api/router.py | 2 - backend/app/admin/api/v1/auth/auth.py | 9 +- backend/app/admin/api/v1/auth/captcha.py | 5 +- backend/app/admin/api/v1/log/__init__.py | 9 - backend/app/admin/api/v1/log/login_log.py | 53 - backend/app/admin/api/v1/log/opera_log.py | 53 - backend/app/admin/api/v1/sys/__init__.py | 4 + backend/app/admin/api/v1/sys/file.py | 21 + backend/app/admin/api/v1/sys/plugin.py | 78 + backend/app/admin/api/v1/sys/user.py | 16 +- backend/app/admin/crud/crud_login_log.py | 64 - backend/app/admin/crud/crud_opera_log.py | 74 - backend/app/admin/crud/crud_user.py | 79 +- .../admin/crud/crud_user_password_history.py | 34 + backend/app/admin/model/__init__.py | 3 +- backend/app/admin/model/login_log.py | 35 - backend/app/admin/model/opera_log.py | 38 - .../app/admin/model/user_password_history.py | 24 + backend/app/admin/schema/login_log.py | 46 - backend/app/admin/schema/opera_log.py | 54 - backend/app/admin/schema/user.py | 23 - .../app/admin/schema/user_password_history.py | 14 + backend/app/admin/service/auth_service.py | 46 +- .../app/admin/service/login_log_service.py | 90 - .../app/admin/service/opera_log_service.py | 72 - backend/app/admin/service/plugin_service.py | 119 + .../service/user_password_history_service.py | 126 ++ backend/app/admin/service/user_service.py | 40 +- backend/app/admin/utils/cache.py | 22 + backend/app/admin/utils/password_security.py | 17 +- backend/cli.py | 257 ++- .../common/{prometheus => cache}/__init__.py | 0 backend/common/cache/decorator.py | 233 ++ backend/common/cache/local.py | 56 + backend/common/cache/pubsub.py | 112 + backend/common/context.py | 2 + backend/common/enums.py | 64 - backend/common/exception/errors.py | 1 + backend/common/log.py | 39 +- backend/common/prometheus/instruments.py | 35 - backend/common/queue.py | 31 - backend/common/security/jwt.py | 12 +- backend/core/conf.py | 57 +- backend/core/registrar.py | 40 +- backend/middleware/access_middleware.py | 7 +- backend/middleware/jwt_auth_middleware.py | 8 +- backend/middleware/opera_log_middleware.py | 258 +-- backend/plugin/config/plugin.toml | 18 +- .../plugin/config/service/config_service.py | 13 +- backend/plugin/config/sql/mysql/destroy.sql | 5 + .../config/sql/mysql/destroy_snowflake.sql | 5 + backend/plugin/config/sql/mysql/init.sql | 13 + .../config/sql/mysql/init_snowflake.sql | 9 + .../plugin/config/sql/postgresql/destroy.sql | 7 + .../sql/postgresql/destroy_snowflake.sql | 5 + backend/plugin/config/sql/postgresql/init.sql | 17 + .../config/sql/postgresql/init_snowflake.sql | 9 + backend/plugin/core.py | 39 +- backend/plugin/installer.py | 41 +- backend/plugin/requirements.py | 59 +- backend/run.py | 17 +- backend/scripts/format.sh | 2 +- .../sql/mysql/init_snowflake_test_data.sql | 4 +- backend/sql/mysql/init_test_data.sql | 2 +- .../postgresql/init_snowflake_test_data.sql | 4 +- backend/sql/postgresql/init_test_data.sql | 1 - backend/utils/build_tree.py | 132 -- backend/utils/console.py | 29 +- backend/utils/dynamic_config.py | 46 +- backend/utils/dynamic_import.py | 8 +- backend/utils/file_ops.py | 11 +- backend/utils/format.py | 12 +- backend/utils/limiter.py | 84 +- backend/utils/otel.py | 102 - backend/utils/pattern_validate.py | 15 +- backend/utils/request_parse.py | 1 + backend/utils/serializers.py | 420 ++-- backend/utils/sql_parser.py | 18 +- backend/utils/timezone.py | 8 +- backend/utils/trace_id.py | 22 - deploy/backend/docker-compose/.env.docker | 14 +- .../grafana/dashboards/fba_server.json | 1892 ---------------- deploy/backend/grafana/fba_config.alloy | 158 -- deploy/backend/grafana/fba_dashboards.yml | 36 - deploy/backend/grafana/fba_datasource.yml | 164 -- deploy/backend/grafana/fba_grafana.ini | 49 - deploy/backend/grafana/fba_prometheus.yml | 13 - deploy/backend/grafana/fba_tempo.yml | 127 -- deploy/backend/supervisor/fba_server.conf | 2 +- deploy/backend/supervisor/supervisord.conf | 2 +- docker-compose.yml | 130 +- pyproject.toml | 304 ++- requirements.txt | 167 +- uv.lock | 1973 +++++++++-------- 109 files changed, 4622 insertions(+), 6612 deletions(-) create mode 100644 .github/workflows/version.yml create mode 100644 .schemas/plugin.schema.json create mode 100644 SLIM_GUIDE.md delete mode 100644 backend/app/admin/api/v1/log/__init__.py delete mode 100644 backend/app/admin/api/v1/log/login_log.py delete mode 100644 backend/app/admin/api/v1/log/opera_log.py create mode 100644 backend/app/admin/api/v1/sys/file.py create mode 100644 backend/app/admin/api/v1/sys/plugin.py delete mode 100644 backend/app/admin/crud/crud_login_log.py delete mode 100644 backend/app/admin/crud/crud_opera_log.py create mode 100644 backend/app/admin/crud/crud_user_password_history.py delete mode 100644 backend/app/admin/model/login_log.py delete mode 100644 backend/app/admin/model/opera_log.py create mode 100644 backend/app/admin/model/user_password_history.py delete mode 100644 backend/app/admin/schema/login_log.py delete mode 100644 backend/app/admin/schema/opera_log.py create mode 100644 backend/app/admin/schema/user_password_history.py delete mode 100644 backend/app/admin/service/login_log_service.py delete mode 100644 backend/app/admin/service/opera_log_service.py create mode 100644 backend/app/admin/service/plugin_service.py create mode 100644 backend/app/admin/service/user_password_history_service.py create mode 100644 backend/app/admin/utils/cache.py rename backend/common/{prometheus => cache}/__init__.py (100%) create mode 100644 backend/common/cache/decorator.py create mode 100644 backend/common/cache/local.py create mode 100644 backend/common/cache/pubsub.py delete mode 100644 backend/common/prometheus/instruments.py delete mode 100644 backend/common/queue.py create mode 100644 backend/plugin/config/sql/mysql/destroy.sql create mode 100644 backend/plugin/config/sql/mysql/destroy_snowflake.sql create mode 100644 backend/plugin/config/sql/postgresql/destroy.sql create mode 100644 backend/plugin/config/sql/postgresql/destroy_snowflake.sql delete mode 100644 backend/utils/build_tree.py delete mode 100644 backend/utils/otel.py delete mode 100644 deploy/backend/grafana/dashboards/fba_server.json delete mode 100644 deploy/backend/grafana/fba_config.alloy delete mode 100644 deploy/backend/grafana/fba_dashboards.yml delete mode 100644 deploy/backend/grafana/fba_datasource.yml delete mode 100644 deploy/backend/grafana/fba_grafana.ini delete mode 100644 deploy/backend/grafana/fba_prometheus.yml delete mode 100644 deploy/backend/grafana/fba_tempo.yml diff --git a/.dockerignore b/.dockerignore index f6c19fe..7148e02 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,3 +10,4 @@ venv/ .pytest_cache/ .claude/ .serena/ +.logs/ diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 0e10c7f..0a1e842 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -1,16 +1,18 @@ name: Release changelog on: - push: - tags: - - v* + workflow_run: + workflows: ['Check version'] + types: + - completed jobs: changelog: runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: master diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 71baf03..c86ff3c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,10 +15,10 @@ jobs: python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14' ] fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 - name: Set up Python ${{ matrix.python-version }} run: uv python install ${{ matrix.python-version }} diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 0000000..707d006 --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,25 @@ +name: Check version + +on: + push: + tags: + - v* + +jobs: + check-version: + name: check version + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Set up Python + run: uv python install 3.13 + + - name: Check the package version + uses: samuelcolvin/check-python-version@v5 + with: + version_file_path: backend/__init__.py diff --git a/.gitignore b/.gitignore index 842ccec..79de28b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ venv/ .pytest_cache/ .claude/ .serena/ +.agents/ +.logs/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a7ab3b1..936fcf7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,8 +10,16 @@ repos: - id: check-yaml - id: check-toml + - repo: https://github.com/tombi-toml/tombi-pre-commit + rev: v0.9.2 + hooks: + - id: tombi-lint + args: ["--offline"] + - id: tombi-format + args: ["--offline"] + - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.14.13 + rev: v0.15.5 hooks: - id: ruff-check args: @@ -21,7 +29,7 @@ repos: - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.9.26 + rev: 0.10.9 hooks: - id: uv-lock - id: uv-export diff --git a/.ruff.toml b/.ruff.toml index 9f4d8c3..3d4c259 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -7,145 +7,145 @@ required-version = ">=0.13.0" [lint] select = [ - "FAST", - "ANN001", - "ANN201", - "ANN202", - "ANN204", - "ANN205", - "ANN206", - "ASYNC110", - "ASYNC116", - "ASYNC210", - "ASYNC212", - "ASYNC230", - "ASYNC240", - "ASYNC250", - "ASYNC251", - "S310", - "FBT001", - "FBT002", - "B002", - "B005", - "B006", - "B007", - "B008", - "B009", - "B010", - "B013", - "B014", - "B019", - "B020", - "B021", - "B024", - "B025", - "B026", - "B027", - "B039", - "COM", - "C402", - "C403", - "C404", - "C408", - "C410", - "C411", - "C414", - "C416", - "C417", - "C418", - "C419", - "C420", - "DTZ", - "EXE", - "ISC001", - "ISC002", - "ISC003", - "PIE", - "PYI009", - "PYI010", - "PYI011", - "PYI012", - "PYI013", - "PYI016", - "PYI017", - "PYI019", - "PYI020", - "PYI021", - "PYI024", - "PYI026", - "PYI030", - "PYI033", - "PYI034", - "PYI036", - "PYI041", - "PYI042", - "PYI055", - "PYI061", - "PYI062", - "PYI063", - "Q001", - "Q002", - "RSE102", - "RET501", - "RET505", - "RET506", - "RET507", - "RET508", - "SIM101", - "SIM102", - "SIM103", - "SIM107", - "SIM108", - "SIM109", - "SIM110", - "SIM114", - "SIM115", - "SIM201", - "SIM202", - "SIM210", - "SIM211", - "SIM212", - "SIM300", - "SIM401", - "SIM910", - "TID252", - "TC", - "FLY", - "I", - "C901", - "N", - "PERF", - "E", - "W", - "D404", - "D417", - "D419", - "F", - "PGH", - "PLC1901", - "UP", - "FURB", - "RUF", - "TRY", + "FAST", + "ANN001", + "ANN201", + "ANN202", + "ANN204", + "ANN205", + "ANN206", + "ASYNC110", + "ASYNC116", + "ASYNC210", + "ASYNC212", + "ASYNC230", + "ASYNC240", + "ASYNC250", + "ASYNC251", + "S310", + "FBT001", + "FBT002", + "B002", + "B005", + "B006", + "B007", + "B008", + "B009", + "B010", + "B013", + "B014", + "B019", + "B020", + "B021", + "B024", + "B025", + "B026", + "B027", + "B039", + "COM", + "C402", + "C403", + "C404", + "C408", + "C410", + "C411", + "C414", + "C416", + "C417", + "C418", + "C419", + "C420", + "DTZ", + "EXE", + "ISC001", + "ISC002", + "ISC003", + "PIE", + "PYI009", + "PYI010", + "PYI011", + "PYI012", + "PYI013", + "PYI016", + "PYI017", + "PYI019", + "PYI020", + "PYI021", + "PYI024", + "PYI026", + "PYI030", + "PYI033", + "PYI034", + "PYI036", + "PYI041", + "PYI042", + "PYI055", + "PYI061", + "PYI062", + "PYI063", + "Q001", + "Q002", + "RSE102", + "RET501", + "RET505", + "RET506", + "RET507", + "RET508", + "SIM101", + "SIM102", + "SIM103", + "SIM107", + "SIM108", + "SIM109", + "SIM110", + "SIM114", + "SIM115", + "SIM201", + "SIM202", + "SIM210", + "SIM211", + "SIM212", + "SIM300", + "SIM401", + "SIM910", + "TID252", + "TC", + "FLY", + "I", + "C901", + "N", + "PERF", + "E", + "W", + "D404", + "D417", + "D419", + "F", + "PGH", + "PLC1901", + "UP", + "FURB", + "RUF", + "TRY", ] ignore = [ - "COM812", - "PGH003", - "RUF001", - "RUF002", - "RUF003", - "RUF006", - "RUF012", - "RUF067", - "TRY400", - "TRY003", - "TRY301" + "COM812", + "PGH003", + "RUF001", + "RUF002", + "RUF003", + "RUF006", + "RUF012", + "RUF067", + "TRY400", + "TRY003", + "TRY301" ] [lint.per-file-ignores] "**/model/*.py" = ["TC003"] "backend/common/socketio/server.py" = ["ANN001"] -"backend/common/exception/exception_handler.py" = ["ANN202","RUF029"] +"backend/common/exception/exception_handler.py" = ["ANN202", "RUF029"] [lint.flake8-pytest-style] parametrize-names-type = "list" @@ -156,7 +156,10 @@ parametrize-values-type = "list" inline-quotes = "single" [lint.flake8-type-checking] -runtime-evaluated-base-classes = ["pydantic.BaseModel", "sqlalchemy.orm.DeclarativeBase"] +runtime-evaluated-base-classes = [ + "pydantic.BaseModel", + "sqlalchemy.orm.DeclarativeBase" +] [lint.flake8-unused-arguments] ignore-variadic-names = true diff --git a/.schemas/plugin.schema.json b/.schemas/plugin.schema.json new file mode 100644 index 0000000..bec1046 --- /dev/null +++ b/.schemas/plugin.schema.json @@ -0,0 +1,138 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FBA Plugin Manifest Schema", + "description": "JSON Schema for FastAPI Best Architecture plugin.toml files. See: https://fastapi-practices.github.io/fastapi_best_architecture_docs/plugin/dev.html", + + "type": "object", + "required": ["plugin", "app"], + "additionalProperties": false, + + "properties": { + "plugin": { + "type": "object", + "description": "Plugin metadata", + "required": ["summary", "version", "description", "author"], + "additionalProperties": false, + "x-tombi-table-keys-order": "schema", + "properties": { + "icon": { + "type": "string", + "description": "Icon path (plugin repository icon path or URL)" + }, + "summary": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Brief summary (1-100 characters)" + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "Plugin version (semver format: x.y.z)" + }, + "description": { + "type": "string", + "minLength": 1, + "maxLength": 500, + "description": "Detailed description (1-500 characters)" + }, + "author": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "description": "Plugin author (1-50 characters)" + }, + "tags": { + "type": "array", + "description": "Plugin tags for categorization (will be required in next major version)", + "items": { + "type": "string", + "enum": ["ai", "mcp", "agent", "auth", "storage", "notification", "task", "payment", "other"] + }, + "x-tombi-array-values-order": "ascending" + }, + "database": { + "type": "array", + "description": "Supported databases (will be required in next major version)", + "items": { + "type": "string", + "enum": ["mysql", "postgresql"] + }, + "x-tombi-array-values-order": "ascending" + } + } + }, + + "app": { + "type": "object", + "description": "Application configuration. For app-level plugins: use 'router'. For extend-level plugins: use 'extend'.", + "additionalProperties": false, + "minProperties": 1, + "x-tombi-table-keys-order": "schema", + "properties": { + "extend": { + "type": "string", + "minLength": 1, + "description": "Parent app folder name (for extension-level plugins)" + }, + "router": { + "type": "array", + "minItems": 1, + "description": "Router instances (for application-level plugins)", + "items": { + "type": "string", + "minLength": 1 + }, + "x-tombi-array-values-order": "version-sort" + } + } + }, + + "settings": { + "type": "object", + "description": "Plugin base configuration (hot-pluggable, uppercase keys only)", + "x-tombi-additional-key-label": "SETTING_NAME", + "x-tombi-table-keys-order": "ascending", + "propertyNames": { + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "additionalProperties": { + "oneOf": [ + { "type": "string" }, + { "type": "number" }, + { "type": "boolean" } + ] + } + }, + + "api": { + "type": "object", + "description": "API endpoint configurations (for extension-level plugins). The key (e.g., 'xxx' in api.xxx) corresponds to the API filename without extension.", + "x-tombi-additional-key-label": "api_filename", + "x-tombi-table-keys-order": "ascending", + "minProperties": 1, + "propertyNames": { + "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" + }, + "additionalProperties": { + "type": "object", + "required": ["prefix", "tags"], + "additionalProperties": false, + "x-tombi-table-keys-order": {"properties": "schema"}, + "properties": { + "prefix": { + "type": "string", + "minLength": 1, + "pattern": "^/[a-zA-Z0-9_/-]*$", + "description": "URL prefix for the API (must start with '/', allowed chars: a-z, A-Z, 0-9, _, -, /)" + }, + "tags": { + "type": "string", + "minLength": 1, + "description": "OpenAPI tags for Swagger documentation" + } + } + } + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 801691f..7625085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,208 @@ + +# [v1.13.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.13.1) - 2026-03-08 + +## What's Changed +* Update changelog for v1.13.0 by [@wu-clan](https://github.com/wu-clan) in [#1078](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1078) +* Fix task issues and update crontab expressions by [@wu-clan](https://github.com/wu-clan) in [#1080](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1080) +* Add redis client native OTEL observability by [@wu-clan](https://github.com/wu-clan) in [#1082](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1082) +* Add data permission rule value template variable by [@wu-clan](https://github.com/wu-clan) in [#1081](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1081) +* Update SQL scripts related to data permissions by [@wu-clan](https://github.com/wu-clan) in [#1084](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1084) +* Remove the sponsorship from the readme by [@wu-clan](https://github.com/wu-clan) in [#1085](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1085) +* Fix query sorting based on model sort column by [@wu-clan](https://github.com/wu-clan) in [#1083](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1083) +* Fix uninstall plugin requirements command by [@wu-clan](https://github.com/wu-clan) in [#1087](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1087) +* Add remove plugin and formatting code CLI by [@wu-clan](https://github.com/wu-clan) in [#1088](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1088) +* Refactor the menu SQL definition in SQL scripts by [@wu-clan](https://github.com/wu-clan) in [#1092](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1092) +* Add destroy SQL scripts for multiple plugins by [@wu-clan](https://github.com/wu-clan) in [#1093](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1093) +* Update the upload file type validation logic by [@SmallGarbage](https://github.com/SmallGarbage) in [#1091](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1091) +* Optimize the CLI command console output style by [@wu-clan](https://github.com/wu-clan) in [#1094](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1094) +* Fix menu SQL in mysql SQL scripts by [@wu-clan](https://github.com/wu-clan) in [#1096](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1096) +* Update project name symbol in multiple files by [@wu-clan](https://github.com/wu-clan) in [#1097](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1097) +* Update utc zoneinfo to datetime timezone by [@wu-clan](https://github.com/wu-clan) in [#1099](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1099) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#1100](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1100) +* Update the version number to 1.13.1 by [@wu-clan](https://github.com/wu-clan) in [#1102](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1102) + +## New Contributors +* [@SmallGarbage](https://github.com/SmallGarbage) made their first contribution in [#1091](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1091) + +**Full Changelog**: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.13.0...v1.13.1 + +## Contributors + +@SmallGarbage +@wu-clan + +[Changes][v1.13.1] + + + +# [v1.13.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.13.0) - 2026-02-14 + +## What's Changed +* Update changelog for v1.12.3 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1009](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1009) +* Update the JWT for easier scaling by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1011](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1011) +* Update the database and Redis for easier scaling by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1015](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1015) +* Fix database calls in init CLI command by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1016](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1016) +* Fix install plugin dependencies in docker container by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1017](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1017) +* Fix database engine in auto init CLI command by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1018](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1018) +* Rename locale to locales to avoid library conflict by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1021](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1021) +* Optimize dynamic config loading implementation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1022](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1022) +* Optimize i18n language file directory structure by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1023](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1023) +* Fix snowflake type primary key field serializer by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1024](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1024) +* Optimize code generation data processing by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1020](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1020) +* Update the i18n file storage directory by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1027](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1027) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1029](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1029) +* Fix custom filename in code generation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1030](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1030) +* Add router jinja template for code generation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1031](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1031) +* Allow to add config in plugin toml by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1033](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1033) +* Refactor code generation and plugin hot reloading by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1032](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1032) +* Add injection app routes to code generation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1034](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1034) +* Remove deprecated parameter for code generation business by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1035](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1035) +* Add code generation preview mode CLI command by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1036](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1036) +* Update plugins and adapt to new features by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1040](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1040) +* Add data validator for plugin config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1041](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1041) +* Fix code generation app route injection by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1042](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1042) +* Update the plugin git address pattern by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1047](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1047) +* Update plugin and code generation subprocess output by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1048](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1048) +* Add the alembic related operation CLI by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1049](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1049) +* Update task path resolution and docker variables by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1050](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1050) +* Update redis and server health monitor data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1051](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1051) +* Add multi level caching and optimize caching by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1054](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1054) +* Add granian and celery metrics collection by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1057](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1057) +* Fix relational insertion error for empty lists by [@SoulEater](https://github.com/SoulEater) in [fastapi-practices/fastapi_best_architecture#1056](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1056) +* Optimize the serialization of join query results by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1058](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1058) +* Update alembic and ruff config to pyproject.toml by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1060](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1060) +* Add tombi toml and bump pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1061](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1061) +* Add tombi and plugin JSON schema config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1062](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1062) +* Fix multi level cache key build and usage by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1066](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1066) +* Add log mount to docker compose by [@yzbf-lin](https://github.com/yzbf-lin) in [fastapi-practices/fastapi_best_architecture#1063](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1063) +* Fix captcha and user validation order logic error by [@mic1on](https://github.com/mic1on) in [fastapi-practices/fastapi_best_architecture#1065](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1065) +* Add multi level cache for dynamic config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1067](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1067) +* Add version check and update workflows by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1068](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1068) +* Add cache key chaining syntax support by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1069](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1069) +* Refactor the interface rate limiter implementation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1072](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1072) +* Fix dynamic config cache serialization by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1075](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1075) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1076](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1076) +* Fix missing parent class init in base exception by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1077](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1077) +* Update the version number to 1.13.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1079](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1079) + +## New Contributors +* [@SoulEater](https://github.com/SoulEater) made their first contribution in [fastapi-practices/fastapi_best_architecture#1056](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1056) +* [@mic1on](https://github.com/mic1on) made their first contribution in [fastapi-practices/fastapi_best_architecture#1065](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1065) + +**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.12.3...v1.13.0 + +## Contributors + +@SoulEater +@mic1on +@wu-clan +@yzbf-lin + +[Changes][v1.13.0] + + + +# [v1.12.3](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.12.3) - 2026-01-13 + +## What's Changed +* Update changelog for v1.12.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#995](https://github.com/fastapi-practices/fastapi_best_architecture/pull/995) +* Update login log request header column length by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#996](https://github.com/fastapi-practices/fastapi_best_architecture/pull/996) +* Fix opera log non-json data overload by [@shj366](https://github.com/shj366) in [fastapi-practices/fastapi_best_architecture#998](https://github.com/fastapi-practices/fastapi_best_architecture/pull/998) +* Remove the opera log desensitization asynchronous by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#999](https://github.com/fastapi-practices/fastapi_best_architecture/pull/999) +* Update redis and server monitor implementations by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1000](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1000) +* Optimize definitions of multiple utility functions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1001](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1001) +* Update code generation part file naming by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1002](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1002) +* Update nickname generation when create user by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1004](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1004) +* Update the plugin dependency install method by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1007](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1007) +* Update i18n language storage and loading by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#1008](https://github.com/fastapi-practices/fastapi_best_architecture/pull/1008) + +## New Contributors +* [@shj366](https://github.com/shj366) made their first contribution in [fastapi-practices/fastapi_best_architecture#998](https://github.com/fastapi-practices/fastapi_best_architecture/pull/998) + +**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.12.2...v1.12.3 + +## Contributors + +@shj366 +@wu-clan + +[Changes][v1.12.3] + + + +# [v1.12.2](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.12.2) - 2026-01-07 + +## What's Changed +* Update changelog for v1.12.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#983](https://github.com/fastapi-practices/fastapi_best_architecture/pull/983) +* Fix environment variable file auto init by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#985](https://github.com/fastapi-practices/fastapi_best_architecture/pull/985) +* Simplify the desensitization of operation log data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#987](https://github.com/fastapi-practices/fastapi_best_architecture/pull/987) +* Remove invalid configs of operation log by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#988](https://github.com/fastapi-practices/fastapi_best_architecture/pull/988) +* Fix operation log queue status management by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#989](https://github.com/fastapi-practices/fastapi_best_architecture/pull/989) +* Fix SQL scripts error in config plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#991](https://github.com/fastapi-practices/fastapi_best_architecture/pull/991) +* Fix the key of the refresh token removed by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#993](https://github.com/fastapi-practices/fastapi_best_architecture/pull/993) +* Remove Linux Do OAuth2 login by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#994](https://github.com/fastapi-practices/fastapi_best_architecture/pull/994) + + +**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.12.1...v1.12.2 + +## Contributors + +@wu-clan + +[Changes][v1.12.2] + + + +# [v1.12.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.12.1) - 2025-12-31 + +## What's Changed +* Update changelog for v1.12.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#963](https://github.com/fastapi-practices/fastapi_best_architecture/pull/963) +* Update Grafana security and user default config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#964](https://github.com/fastapi-practices/fastapi_best_architecture/pull/964) +* Rename the pre start script to migrate by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#965](https://github.com/fastapi-practices/fastapi_best_architecture/pull/965) +* Add code generation and notice SQL scripts by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#966](https://github.com/fastapi-practices/fastapi_best_architecture/pull/966) +* Fix support for special character passwords by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#968](https://github.com/fastapi-practices/fastapi_best_architecture/pull/968) +* Add an independent contribution document by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#971](https://github.com/fastapi-practices/fastapi_best_architecture/pull/971) +* Fix i18n target language error when concurrent by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#970](https://github.com/fastapi-practices/fastapi_best_architecture/pull/970) +* Add observability instrument for redis client by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#972](https://github.com/fastapi-practices/fastapi_best_architecture/pull/972) +* Add OTEL semantic specification metrics config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#973](https://github.com/fastapi-practices/fastapi_best_architecture/pull/973) +* Fix case where the user agent was empty by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#976](https://github.com/fastapi-practices/fastapi_best_architecture/pull/976) +* Optimize login log database session calls by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#977](https://github.com/fastapi-practices/fastapi_best_architecture/pull/977) +* Add the auto init project CLI command by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#978](https://github.com/fastapi-practices/fastapi_best_architecture/pull/978) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#979](https://github.com/fastapi-practices/fastapi_best_architecture/pull/979) +* Add observability instrument for httpx request by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#980](https://github.com/fastapi-practices/fastapi_best_architecture/pull/980) +* Update git and docker ignore files by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#981](https://github.com/fastapi-practices/fastapi_best_architecture/pull/981) + + +**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.12.0...v1.12.1 + +## Contributors + +@wu-clan + +[Changes][v1.12.1] + + -# [v1.12.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.12.0) - 2025-12-15 +# [v1.12.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.12.0) - 2025-12-15 ## What's Changed -* Update changelog for v1.11.2 by [@wu-clan](https://github.com/wu-clan) in [#942](https://github.com/fastapi-practices/fastapi_best_architecture/pull/942) -* Update celery related docker container independence by [@wu-clan](https://github.com/wu-clan) in [#943](https://github.com/fastapi-practices/fastapi_best_architecture/pull/943) -* Fix super value in update user permissions by [@wu-clan](https://github.com/wu-clan) in [#948](https://github.com/fastapi-practices/fastapi_best_architecture/pull/948) -* Optimize data permission logic and usage by [@wu-clan](https://github.com/wu-clan) in [#947](https://github.com/fastapi-practices/fastapi_best_architecture/pull/947) -* Update pre-commit to prek in pre-commit script by [@wu-clan](https://github.com/wu-clan) in [#949](https://github.com/fastapi-practices/fastapi_best_architecture/pull/949) -* Optimize the coupling of user social plugin by [@wu-clan](https://github.com/wu-clan) in [#950](https://github.com/fastapi-practices/fastapi_best_architecture/pull/950) -* Add the database primary key mode config by [@wu-clan](https://github.com/wu-clan) in [#953](https://github.com/fastapi-practices/fastapi_best_architecture/pull/953) -* Optimize the coupling of code generation CLI by [@wu-clan](https://github.com/wu-clan) in [#951](https://github.com/fastapi-practices/fastapi_best_architecture/pull/951) -* Add CLI init project database support by [@wu-clan](https://github.com/wu-clan) in [#952](https://github.com/fastapi-practices/fastapi_best_architecture/pull/952) -* Update the init project database CLI to subcommand by [@wu-clan](https://github.com/wu-clan) in [#954](https://github.com/fastapi-practices/fastapi_best_architecture/pull/954) -* Fix CLI command for code generation by [@wu-clan](https://github.com/wu-clan) in [#956](https://github.com/fastapi-practices/fastapi_best_architecture/pull/956) -* Fix the IP address in the request log by [@wuyuemushi](https://github.com/wuyuemushi) in [#959](https://github.com/fastapi-practices/fastapi_best_architecture/pull/959) -* Add the Grafana observability suite by [@wu-clan](https://github.com/wu-clan) in [#961](https://github.com/fastapi-practices/fastapi_best_architecture/pull/961) -* Update the version number to 1.12.0 by [@wu-clan](https://github.com/wu-clan) in [#962](https://github.com/fastapi-practices/fastapi_best_architecture/pull/962) +* Update changelog for v1.11.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#942](https://github.com/fastapi-practices/fastapi_best_architecture/pull/942) +* Update celery related docker container independence by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#943](https://github.com/fastapi-practices/fastapi_best_architecture/pull/943) +* Fix super value in update user permissions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#948](https://github.com/fastapi-practices/fastapi_best_architecture/pull/948) +* Optimize data permission logic and usage by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#947](https://github.com/fastapi-practices/fastapi_best_architecture/pull/947) +* Update pre-commit to prek in pre-commit script by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#949](https://github.com/fastapi-practices/fastapi_best_architecture/pull/949) +* Optimize the coupling of user social plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#950](https://github.com/fastapi-practices/fastapi_best_architecture/pull/950) +* Add the database primary key mode config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#953](https://github.com/fastapi-practices/fastapi_best_architecture/pull/953) +* Optimize the coupling of code generation CLI by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#951](https://github.com/fastapi-practices/fastapi_best_architecture/pull/951) +* Add CLI init project database support by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#952](https://github.com/fastapi-practices/fastapi_best_architecture/pull/952) +* Update the init project database CLI to subcommand by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#954](https://github.com/fastapi-practices/fastapi_best_architecture/pull/954) +* Fix CLI command for code generation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#956](https://github.com/fastapi-practices/fastapi_best_architecture/pull/956) +* Fix the IP address in the request log by [@wuyuemushi](https://github.com/wuyuemushi) in [fastapi-practices/fastapi_best_architecture#959](https://github.com/fastapi-practices/fastapi_best_architecture/pull/959) +* Add the Grafana observability suite by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#961](https://github.com/fastapi-practices/fastapi_best_architecture/pull/961) +* Update the version number to 1.12.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#962](https://github.com/fastapi-practices/fastapi_best_architecture/pull/962) ## New Contributors -* [@wuyuemushi](https://github.com/wuyuemushi) made their first contribution in [#959](https://github.com/fastapi-practices/fastapi_best_architecture/pull/959) +* [@wuyuemushi](https://github.com/wuyuemushi) made their first contribution in [fastapi-practices/fastapi_best_architecture#959](https://github.com/fastapi-practices/fastapi_best_architecture/pull/959) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.2...v1.12.0 @@ -31,23 +215,23 @@ -# [v1.11.2](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.11.2) - 2025-11-28 +# [v1.11.2](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.11.2) - 2025-11-28 ## What's Changed -* Update changelog for v1.11.1 by [@wu-clan](https://github.com/wu-clan) in [#923](https://github.com/fastapi-practices/fastapi_best_architecture/pull/923) -* Fix typos in data rule sql scripts by [@wu-clan](https://github.com/wu-clan) in [#926](https://github.com/fastapi-practices/fastapi_best_architecture/pull/926) -* Restore captcha uuid naming in the login params by [@wu-clan](https://github.com/wu-clan) in [#928](https://github.com/fastapi-practices/fastapi_best_architecture/pull/928) -* Add distributed deployment support for snowflake ID by [@downdawn](https://github.com/downdawn) in [#927](https://github.com/fastapi-practices/fastapi_best_architecture/pull/927) -* Add env reqs for plugin install and uninstall by [@wu-clan](https://github.com/wu-clan) in [#929](https://github.com/fastapi-practices/fastapi_best_architecture/pull/929) -* Optimize the use of some LRU caches by [@wu-clan](https://github.com/wu-clan) in [#932](https://github.com/fastapi-practices/fastapi_best_architecture/pull/932) -* Update the i18n language file init location by [@wu-clan](https://github.com/wu-clan) in [#934](https://github.com/fastapi-practices/fastapi_best_architecture/pull/934) -* Fix get column types in code generation by [@wu-clan](https://github.com/wu-clan) in [#935](https://github.com/fastapi-practices/fastapi_best_architecture/pull/935) -* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#936](https://github.com/fastapi-practices/fastapi_best_architecture/pull/936) -* Update the files interface filename to file by [@wu-clan](https://github.com/wu-clan) in [#937](https://github.com/fastapi-practices/fastapi_best_architecture/pull/937) -* Update task application interface definitions by [@wu-clan](https://github.com/wu-clan) in [#938](https://github.com/fastapi-practices/fastapi_best_architecture/pull/938) -* Update code generation interface definitions by [@wu-clan](https://github.com/wu-clan) in [#939](https://github.com/fastapi-practices/fastapi_best_architecture/pull/939) -* Update the fba run CLI command output by [@wu-clan](https://github.com/wu-clan) in [#941](https://github.com/fastapi-practices/fastapi_best_architecture/pull/941) -* Update the version number to 1.11.2 by [@wu-clan](https://github.com/wu-clan) in [#940](https://github.com/fastapi-practices/fastapi_best_architecture/pull/940) +* Update changelog for v1.11.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#923](https://github.com/fastapi-practices/fastapi_best_architecture/pull/923) +* Fix typos in data rule sql scripts by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#926](https://github.com/fastapi-practices/fastapi_best_architecture/pull/926) +* Restore captcha uuid naming in the login params by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#928](https://github.com/fastapi-practices/fastapi_best_architecture/pull/928) +* Add distributed deployment support for snowflake ID by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#927](https://github.com/fastapi-practices/fastapi_best_architecture/pull/927) +* Add env reqs for plugin install and uninstall by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#929](https://github.com/fastapi-practices/fastapi_best_architecture/pull/929) +* Optimize the use of some LRU caches by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#932](https://github.com/fastapi-practices/fastapi_best_architecture/pull/932) +* Update the i18n language file init location by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#934](https://github.com/fastapi-practices/fastapi_best_architecture/pull/934) +* Fix get column types in code generation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#935](https://github.com/fastapi-practices/fastapi_best_architecture/pull/935) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#936](https://github.com/fastapi-practices/fastapi_best_architecture/pull/936) +* Update the files interface filename to file by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#937](https://github.com/fastapi-practices/fastapi_best_architecture/pull/937) +* Update task application interface definitions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#938](https://github.com/fastapi-practices/fastapi_best_architecture/pull/938) +* Update code generation interface definitions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#939](https://github.com/fastapi-practices/fastapi_best_architecture/pull/939) +* Update the fba run CLI command output by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#941](https://github.com/fastapi-practices/fastapi_best_architecture/pull/941) +* Update the version number to 1.11.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#940](https://github.com/fastapi-practices/fastapi_best_architecture/pull/940) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.1...v1.11.2 @@ -61,17 +245,17 @@ -# [v1.11.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.11.1) - 2025-11-16 +# [v1.11.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.11.1) - 2025-11-16 ## What's Changed -* Update changelog for v1.11.0 by [@wu-clan](https://github.com/wu-clan) in [#917](https://github.com/fastapi-practices/fastapi_best_architecture/pull/917) -* Fix missing table in alembic migration by [@wu-clan](https://github.com/wu-clan) in [#920](https://github.com/fastapi-practices/fastapi_best_architecture/pull/920) -* Add user social binding and unbinding by [@wu-clan](https://github.com/wu-clan) in [#919](https://github.com/fastapi-practices/fastapi_best_architecture/pull/919) -* Fix the user list query serialization by [@linrong](https://github.com/linrong) in [#921](https://github.com/fastapi-practices/fastapi_best_architecture/pull/921) -* Update user and login security configs by [@wu-clan](https://github.com/wu-clan) in [#922](https://github.com/fastapi-practices/fastapi_best_architecture/pull/922) +* Update changelog for v1.11.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#917](https://github.com/fastapi-practices/fastapi_best_architecture/pull/917) +* Fix missing table in alembic migration by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#920](https://github.com/fastapi-practices/fastapi_best_architecture/pull/920) +* Add user social binding and unbinding by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#919](https://github.com/fastapi-practices/fastapi_best_architecture/pull/919) +* Fix the user list query serialization by [@linrong](https://github.com/linrong) in [fastapi-practices/fastapi_best_architecture#921](https://github.com/fastapi-practices/fastapi_best_architecture/pull/921) +* Update user and login security configs by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#922](https://github.com/fastapi-practices/fastapi_best_architecture/pull/922) ## New Contributors -* [@linrong](https://github.com/linrong) made their first contribution in [#921](https://github.com/fastapi-practices/fastapi_best_architecture/pull/921) +* [@linrong](https://github.com/linrong) made their first contribution in [fastapi-practices/fastapi_best_architecture#921](https://github.com/fastapi-practices/fastapi_best_architecture/pull/921) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.0...v1.11.1 @@ -84,11 +268,11 @@ -# [v1.11.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.11.0) - 2025-11-12 +# [v1.11.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.11.0) - 2025-11-12 ## What's Changed -* Update changelog for v1.10.4 by [@wu-clan](https://github.com/wu-clan) in [#916](https://github.com/fastapi-practices/fastapi_best_architecture/pull/916) -* Refactor foreign keys and relationships to pure logic by [@wu-clan](https://github.com/wu-clan) in [#901](https://github.com/fastapi-practices/fastapi_best_architecture/pull/901) +* Update changelog for v1.10.4 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#916](https://github.com/fastapi-practices/fastapi_best_architecture/pull/916) +* Refactor foreign keys and relationships to pure logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#901](https://github.com/fastapi-practices/fastapi_best_architecture/pull/901) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.4...v1.11.0 @@ -101,27 +285,27 @@ -# [v1.10.4](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.10.4) - 2025-11-12 +# [v1.10.4](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.10.4) - 2025-11-12 ## What's Changed -* Update changelog for v1.10.3 by [@wu-clan](https://github.com/wu-clan) in [#895](https://github.com/fastapi-practices/fastapi_best_architecture/pull/895) -* Bump fastapi oauth2 from 0.0.1 to 0.0.2 by [@wu-clan](https://github.com/wu-clan) in [#896](https://github.com/fastapi-practices/fastapi_best_architecture/pull/896) -* Update the interface timing accuracy in log by [@wu-clan](https://github.com/wu-clan) in [#897](https://github.com/fastapi-practices/fastapi_best_architecture/pull/897) -* Optimize redis batch get and delete operations by [@wu-clan](https://github.com/wu-clan) in [#899](https://github.com/fastapi-practices/fastapi_best_architecture/pull/899) -* Update the time column type in the task by [@wu-clan](https://github.com/wu-clan) in [#900](https://github.com/fastapi-practices/fastapi_best_architecture/pull/900) -* Add the user social independent enum file by [@wu-clan](https://github.com/wu-clan) in [#902](https://github.com/fastapi-practices/fastapi_best_architecture/pull/902) -* Optimize the request params of the service layer by [@wu-clan](https://github.com/wu-clan) in [#903](https://github.com/fastapi-practices/fastapi_best_architecture/pull/903) -* Optimize the data permission condition build by [@wu-clan](https://github.com/wu-clan) in [#904](https://github.com/fastapi-practices/fastapi_best_architecture/pull/904) -* Fix response status codes in the request logs by [@wu-clan](https://github.com/wu-clan) in [#905](https://github.com/fastapi-practices/fastapi_best_architecture/pull/905) -* Add dept validation to user updates by [@wu-clan](https://github.com/wu-clan) in [#906](https://github.com/fastapi-practices/fastapi_best_architecture/pull/906) -* Update the version number to 1.10.4 by [@wu-clan](https://github.com/wu-clan) in [#907](https://github.com/fastapi-practices/fastapi_best_architecture/pull/907) -* Fix typo in the data permission prompt by [@wu-clan](https://github.com/wu-clan) in [#909](https://github.com/fastapi-practices/fastapi_best_architecture/pull/909) -* Fix user cache cleanup when operating data rules by [@wu-clan](https://github.com/wu-clan) in [#910](https://github.com/fastapi-practices/fastapi_best_architecture/pull/910) -* Fix create and delete department validations by [@wu-clan](https://github.com/wu-clan) in [#911](https://github.com/fastapi-practices/fastapi_best_architecture/pull/911) -* Fix the user menu sidebar parsing by [@wu-clan](https://github.com/wu-clan) in [#912](https://github.com/fastapi-practices/fastapi_best_architecture/pull/912) -* Add user social unbinding account interface by [@wu-clan](https://github.com/wu-clan) in [#913](https://github.com/fastapi-practices/fastapi_best_architecture/pull/913) -* Fix user cache cleanup when updating data scope by [@wu-clan](https://github.com/wu-clan) in [#915](https://github.com/fastapi-practices/fastapi_best_architecture/pull/915) -* Fix dept and menu table subqueries by [@wu-clan](https://github.com/wu-clan) in [#914](https://github.com/fastapi-practices/fastapi_best_architecture/pull/914) +* Update changelog for v1.10.3 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#895](https://github.com/fastapi-practices/fastapi_best_architecture/pull/895) +* Bump fastapi oauth2 from 0.0.1 to 0.0.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#896](https://github.com/fastapi-practices/fastapi_best_architecture/pull/896) +* Update the interface timing accuracy in log by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#897](https://github.com/fastapi-practices/fastapi_best_architecture/pull/897) +* Optimize redis batch get and delete operations by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#899](https://github.com/fastapi-practices/fastapi_best_architecture/pull/899) +* Update the time column type in the task by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#900](https://github.com/fastapi-practices/fastapi_best_architecture/pull/900) +* Add the user social independent enum file by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#902](https://github.com/fastapi-practices/fastapi_best_architecture/pull/902) +* Optimize the request params of the service layer by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#903](https://github.com/fastapi-practices/fastapi_best_architecture/pull/903) +* Optimize the data permission condition build by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#904](https://github.com/fastapi-practices/fastapi_best_architecture/pull/904) +* Fix response status codes in the request logs by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#905](https://github.com/fastapi-practices/fastapi_best_architecture/pull/905) +* Add dept validation to user updates by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#906](https://github.com/fastapi-practices/fastapi_best_architecture/pull/906) +* Update the version number to 1.10.4 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#907](https://github.com/fastapi-practices/fastapi_best_architecture/pull/907) +* Fix typo in the data permission prompt by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#909](https://github.com/fastapi-practices/fastapi_best_architecture/pull/909) +* Fix user cache cleanup when operating data rules by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#910](https://github.com/fastapi-practices/fastapi_best_architecture/pull/910) +* Fix create and delete department validations by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#911](https://github.com/fastapi-practices/fastapi_best_architecture/pull/911) +* Fix the user menu sidebar parsing by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#912](https://github.com/fastapi-practices/fastapi_best_architecture/pull/912) +* Add user social unbinding account interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#913](https://github.com/fastapi-practices/fastapi_best_architecture/pull/913) +* Fix user cache cleanup when updating data scope by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#915](https://github.com/fastapi-practices/fastapi_best_architecture/pull/915) +* Fix dept and menu table subqueries by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#914](https://github.com/fastapi-practices/fastapi_best_architecture/pull/914) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.3...v1.10.4 @@ -134,24 +318,24 @@ -# [v1.10.3](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.10.3) - 2025-10-30 +# [v1.10.3](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.10.3) - 2025-10-30 ## What's Changed -* Update changelog for v1.10.2 by [@wu-clan](https://github.com/wu-clan) in [#873](https://github.com/fastapi-practices/fastapi_best_architecture/pull/873) -* Fix docker default database env variables by [@wu-clan](https://github.com/wu-clan) in [#874](https://github.com/fastapi-practices/fastapi_best_architecture/pull/874) -* Add port for fba sever in docker compose by [@wu-clan](https://github.com/wu-clan) in [#875](https://github.com/fastapi-practices/fastapi_best_architecture/pull/875) -* Update the container naming in docker scripts by [@wu-clan](https://github.com/wu-clan) in [#876](https://github.com/fastapi-practices/fastapi_best_architecture/pull/876) -* Fix the httpurl type compatibility with postgresql by [@wu-clan](https://github.com/wu-clan) in [#877](https://github.com/fastapi-practices/fastapi_best_architecture/pull/877) -* Update the default length of user email column by [@wu-clan](https://github.com/wu-clan) in [#878](https://github.com/fastapi-practices/fastapi_best_architecture/pull/878) -* Update the serializer of httpurl type by [@wu-clan](https://github.com/wu-clan) in [#879](https://github.com/fastapi-practices/fastapi_best_architecture/pull/879) -* Fix the OAuth2 link acquisition in HTTPS by [@wu-clan](https://github.com/wu-clan) in [#881](https://github.com/fastapi-practices/fastapi_best_architecture/pull/881) -* Add Google OAuth2 callback to opera log exclusion by [@wu-clan](https://github.com/wu-clan) in [#882](https://github.com/fastapi-practices/fastapi_best_architecture/pull/882) -* Update the length style of the model columns by [@wu-clan](https://github.com/wu-clan) in [#883](https://github.com/fastapi-practices/fastapi_best_architecture/pull/883) -* Bump dependencies to the latest version by [@wu-clan](https://github.com/wu-clan) in [#890](https://github.com/fastapi-practices/fastapi_best_architecture/pull/890) -* Fix import in code generation api template by [@wu-clan](https://github.com/wu-clan) in [#891](https://github.com/fastapi-practices/fastapi_best_architecture/pull/891) -* Fix celery compatibility with psycopg version by [@wu-clan](https://github.com/wu-clan) in [#892](https://github.com/fastapi-practices/fastapi_best_architecture/pull/892) -* Fix the venv pip availability in Linux by [@wu-clan](https://github.com/wu-clan) in [#893](https://github.com/fastapi-practices/fastapi_best_architecture/pull/893) -* Add the celery rabbitmq vhost config by [@wu-clan](https://github.com/wu-clan) in [#894](https://github.com/fastapi-practices/fastapi_best_architecture/pull/894) +* Update changelog for v1.10.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#873](https://github.com/fastapi-practices/fastapi_best_architecture/pull/873) +* Fix docker default database env variables by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#874](https://github.com/fastapi-practices/fastapi_best_architecture/pull/874) +* Add port for fba sever in docker compose by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#875](https://github.com/fastapi-practices/fastapi_best_architecture/pull/875) +* Update the container naming in docker scripts by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#876](https://github.com/fastapi-practices/fastapi_best_architecture/pull/876) +* Fix the httpurl type compatibility with postgresql by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#877](https://github.com/fastapi-practices/fastapi_best_architecture/pull/877) +* Update the default length of user email column by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#878](https://github.com/fastapi-practices/fastapi_best_architecture/pull/878) +* Update the serializer of httpurl type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#879](https://github.com/fastapi-practices/fastapi_best_architecture/pull/879) +* Fix the OAuth2 link acquisition in HTTPS by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#881](https://github.com/fastapi-practices/fastapi_best_architecture/pull/881) +* Add Google OAuth2 callback to opera log exclusion by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#882](https://github.com/fastapi-practices/fastapi_best_architecture/pull/882) +* Update the length style of the model columns by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#883](https://github.com/fastapi-practices/fastapi_best_architecture/pull/883) +* Bump dependencies to the latest version by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#890](https://github.com/fastapi-practices/fastapi_best_architecture/pull/890) +* Fix import in code generation api template by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#891](https://github.com/fastapi-practices/fastapi_best_architecture/pull/891) +* Fix celery compatibility with psycopg version by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#892](https://github.com/fastapi-practices/fastapi_best_architecture/pull/892) +* Fix the venv pip availability in Linux by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#893](https://github.com/fastapi-practices/fastapi_best_architecture/pull/893) +* Add the celery rabbitmq vhost config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#894](https://github.com/fastapi-practices/fastapi_best_architecture/pull/894) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.2...v1.10.3 @@ -164,13 +348,13 @@ -# [v1.10.2](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.10.2) - 2025-10-21 +# [v1.10.2](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.10.2) - 2025-10-21 ## What's Changed -* Update the changelog for v1.10.1 by [@wu-clan](https://github.com/wu-clan) in [#869](https://github.com/fastapi-practices/fastapi_best_architecture/pull/869) -* Bump rtoml and uvicorn to support python 3.14 by [@wu-clan](https://github.com/wu-clan) in [#871](https://github.com/fastapi-practices/fastapi_best_architecture/pull/871) -* Optimize sqlalchemy types to simplify compatibility by [@wu-clan](https://github.com/wu-clan) in [#870](https://github.com/fastapi-practices/fastapi_best_architecture/pull/870) -* Bump fastapi to remove warning for python 3.14 by [@wu-clan](https://github.com/wu-clan) in [#872](https://github.com/fastapi-practices/fastapi_best_architecture/pull/872) +* Update the changelog for v1.10.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#869](https://github.com/fastapi-practices/fastapi_best_architecture/pull/869) +* Bump rtoml and uvicorn to support python 3.14 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#871](https://github.com/fastapi-practices/fastapi_best_architecture/pull/871) +* Optimize sqlalchemy types to simplify compatibility by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#870](https://github.com/fastapi-practices/fastapi_best_architecture/pull/870) +* Bump fastapi to remove warning for python 3.14 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#872](https://github.com/fastapi-practices/fastapi_best_architecture/pull/872) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.1...v1.10.2 @@ -183,14 +367,14 @@ -# [v1.10.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.10.1) - 2025-10-18 +# [v1.10.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.10.1) - 2025-10-18 ## What's Changed -* Update the changelog for v1.10.0 by [@wu-clan](https://github.com/wu-clan) in [#864](https://github.com/fastapi-practices/fastapi_best_architecture/pull/864) -* Fix tasks related to deleting db logs by [@wu-clan](https://github.com/wu-clan) in [#863](https://github.com/fastapi-practices/fastapi_best_architecture/pull/863) -* Fix the volume in the docker compose script by [@wu-clan](https://github.com/wu-clan) in [#865](https://github.com/fastapi-practices/fastapi_best_architecture/pull/865) -* Update the release changelog workflow by [@wu-clan](https://github.com/wu-clan) in [#867](https://github.com/fastapi-practices/fastapi_best_architecture/pull/867) -* Bump dependencies to the latest version by [@wu-clan](https://github.com/wu-clan) in [#868](https://github.com/fastapi-practices/fastapi_best_architecture/pull/868) +* Update the changelog for v1.10.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#864](https://github.com/fastapi-practices/fastapi_best_architecture/pull/864) +* Fix tasks related to deleting db logs by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#863](https://github.com/fastapi-practices/fastapi_best_architecture/pull/863) +* Fix the volume in the docker compose script by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#865](https://github.com/fastapi-practices/fastapi_best_architecture/pull/865) +* Update the release changelog workflow by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#867](https://github.com/fastapi-practices/fastapi_best_architecture/pull/867) +* Bump dependencies to the latest version by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#868](https://github.com/fastapi-practices/fastapi_best_architecture/pull/868) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.0...v1.10.1 @@ -203,16 +387,16 @@ -# [v1.10.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.10.0) - 2025-10-17 +# [v1.10.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.10.0) - 2025-10-17 ## What's Changed -* Fix the import table cli command by [@wu-clan](https://github.com/wu-clan) in [#860](https://github.com/fastapi-practices/fastapi_best_architecture/pull/860) -* Update unique judgment logic for dict data by [@wu-clan](https://github.com/wu-clan) in [#859](https://github.com/fastapi-practices/fastapi_best_architecture/pull/859) -* Fix the api ninja in code generation by [@wu-clan](https://github.com/wu-clan) in [#858](https://github.com/fastapi-practices/fastapi_best_architecture/pull/858) -* Fix dict plugin pgsql init script missing by [@wu-clan](https://github.com/wu-clan) in [#857](https://github.com/fastapi-practices/fastapi_best_architecture/pull/857) -* Update the changelog for v1.9.0 by [@wu-clan](https://github.com/wu-clan) in [#856](https://github.com/fastapi-practices/fastapi_best_architecture/pull/856) -* Fix ctx in validation exception handler by [@wu-clan](https://github.com/wu-clan) in [#861](https://github.com/fastapi-practices/fastapi_best_architecture/pull/861) -* Update the primary database to postgresql by [@wu-clan](https://github.com/wu-clan) in [#829](https://github.com/fastapi-practices/fastapi_best_architecture/pull/829) +* Fix the import table cli command by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#860](https://github.com/fastapi-practices/fastapi_best_architecture/pull/860) +* Update unique judgment logic for dict data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#859](https://github.com/fastapi-practices/fastapi_best_architecture/pull/859) +* Fix the api ninja in code generation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#858](https://github.com/fastapi-practices/fastapi_best_architecture/pull/858) +* Fix dict plugin pgsql init script missing by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#857](https://github.com/fastapi-practices/fastapi_best_architecture/pull/857) +* Update the changelog for v1.9.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#856](https://github.com/fastapi-practices/fastapi_best_architecture/pull/856) +* Fix ctx in validation exception handler by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#861](https://github.com/fastapi-practices/fastapi_best_architecture/pull/861) +* Update the primary database to postgresql by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#829](https://github.com/fastapi-practices/fastapi_best_architecture/pull/829) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.9.0...v1.10.0 @@ -225,16 +409,16 @@ -# [v1.9.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.9.0) - 2025-10-16 +# [v1.9.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.9.0) - 2025-10-16 ## What's Changed -* Update changelog for v1.8.3 by [@wu-clan](https://github.com/wu-clan) in [#849](https://github.com/fastapi-practices/fastapi_best_architecture/pull/849) -* Bump fastapi and pydantic to the latest by [@wu-clan](https://github.com/wu-clan) in [#851](https://github.com/fastapi-practices/fastapi_best_architecture/pull/851) -* Update superuser verify to dependency injection by [@wu-clan](https://github.com/wu-clan) in [#852](https://github.com/fastapi-practices/fastapi_best_architecture/pull/852) -* Fix the superuser verify missing auth by [@wu-clan](https://github.com/wu-clan) in [#854](https://github.com/fastapi-practices/fastapi_best_architecture/pull/854) -* Update request state usage to context variable by [@wu-clan](https://github.com/wu-clan) in [#853](https://github.com/fastapi-practices/fastapi_best_architecture/pull/853) -* Fix safely of access ctx in exception handlers by [@wu-clan](https://github.com/wu-clan) in [#855](https://github.com/fastapi-practices/fastapi_best_architecture/pull/855) -* Refactor the service layer db session call method by [@wu-clan](https://github.com/wu-clan) in [#850](https://github.com/fastapi-practices/fastapi_best_architecture/pull/850) +* Update changelog for v1.8.3 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#849](https://github.com/fastapi-practices/fastapi_best_architecture/pull/849) +* Bump fastapi and pydantic to the latest by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#851](https://github.com/fastapi-practices/fastapi_best_architecture/pull/851) +* Update superuser verify to dependency injection by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#852](https://github.com/fastapi-practices/fastapi_best_architecture/pull/852) +* Fix the superuser verify missing auth by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#854](https://github.com/fastapi-practices/fastapi_best_architecture/pull/854) +* Update request state usage to context variable by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#853](https://github.com/fastapi-practices/fastapi_best_architecture/pull/853) +* Fix safely of access ctx in exception handlers by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#855](https://github.com/fastapi-practices/fastapi_best_architecture/pull/855) +* Refactor the service layer db session call method by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#850](https://github.com/fastapi-practices/fastapi_best_architecture/pull/850) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.3...v1.9.0 @@ -247,16 +431,16 @@ -# [v1.8.3](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.8.3) - 2025-10-13 +# [v1.8.3](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.8.3) - 2025-10-13 ## What's Changed -* Update changelog for v1.8.2 by [@wu-clan](https://github.com/wu-clan) in [#841](https://github.com/fastapi-practices/fastapi_best_architecture/pull/841) -* Fix the pgsql script in dict plugin by [@wu-clan](https://github.com/wu-clan) in [#842](https://github.com/fastapi-practices/fastapi_best_architecture/pull/842) -* Fix SQL script syntax error in config plugin by [@wu-clan](https://github.com/wu-clan) in [#843](https://github.com/fastapi-practices/fastapi_best_architecture/pull/843) -* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#845](https://github.com/fastapi-practices/fastapi_best_architecture/pull/845) -* Update the ruff rules and format the code by [@wu-clan](https://github.com/wu-clan) in [#846](https://github.com/fastapi-practices/fastapi_best_architecture/pull/846) -* Bump fastapi and sqlalchemy to support python 3.14 by [@wu-clan](https://github.com/wu-clan) in [#847](https://github.com/fastapi-practices/fastapi_best_architecture/pull/847) -* Bump sqlalchemy-crud-plus from 0.12.0 to 0.12.1 by [@wu-clan](https://github.com/wu-clan) in [#848](https://github.com/fastapi-practices/fastapi_best_architecture/pull/848) +* Update changelog for v1.8.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#841](https://github.com/fastapi-practices/fastapi_best_architecture/pull/841) +* Fix the pgsql script in dict plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#842](https://github.com/fastapi-practices/fastapi_best_architecture/pull/842) +* Fix SQL script syntax error in config plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#843](https://github.com/fastapi-practices/fastapi_best_architecture/pull/843) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#845](https://github.com/fastapi-practices/fastapi_best_architecture/pull/845) +* Update the ruff rules and format the code by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#846](https://github.com/fastapi-practices/fastapi_best_architecture/pull/846) +* Bump fastapi and sqlalchemy to support python 3.14 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#847](https://github.com/fastapi-practices/fastapi_best_architecture/pull/847) +* Bump sqlalchemy-crud-plus from 0.12.0 to 0.12.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#848](https://github.com/fastapi-practices/fastapi_best_architecture/pull/848) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.2...v1.8.3 @@ -269,34 +453,34 @@ -# [v1.8.2](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.8.2) - 2025-09-26 +# [v1.8.2](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.8.2) - 2025-09-26 ## What's Changed -* Update changelog for v1.8.1 by [@wu-clan](https://github.com/wu-clan) in [#804](https://github.com/fastapi-practices/fastapi_best_architecture/pull/804) -* Update the swagger docs version to dynamic by [@wu-clan](https://github.com/wu-clan) in [#805](https://github.com/fastapi-practices/fastapi_best_architecture/pull/805) -* Fix the subprocess ensurepip got stuck in Linux by [@byte-voyager](https://github.com/byte-voyager) in [#806](https://github.com/fastapi-practices/fastapi_best_architecture/pull/806) -* Update the offline location information parse by [@wu-clan](https://github.com/wu-clan) in [#807](https://github.com/fastapi-practices/fastapi_best_architecture/pull/807) -* Fix pgsql syntax error in code generation by [@byte-voyager](https://github.com/byte-voyager) in [#808](https://github.com/fastapi-practices/fastapi_best_architecture/pull/808) -* Fix summary default in opera log middleware by [@wu-clan](https://github.com/wu-clan) in [#809](https://github.com/fastapi-practices/fastapi_best_architecture/pull/809) -* Fix special character password support in alembic by [@MortyZhaoy](https://github.com/MortyZhaoy) in [#811](https://github.com/fastapi-practices/fastapi_best_architecture/pull/811) -* Update the startup progress information display by [@wu-clan](https://github.com/wu-clan) in [#812](https://github.com/fastapi-practices/fastapi_best_architecture/pull/812) -* Update the login captcha verify logic by [@wu-clan](https://github.com/wu-clan) in [#815](https://github.com/fastapi-practices/fastapi_best_architecture/pull/815) -* Fix the summary in opera log middleware by [@wu-clan](https://github.com/wu-clan) in [#816](https://github.com/fastapi-practices/fastapi_best_architecture/pull/816) -* Update the dict plugin table structure by [@wu-clan](https://github.com/wu-clan) in [#817](https://github.com/fastapi-practices/fastapi_best_architecture/pull/817) -* Add version number output to the startup CLI by [@wu-clan](https://github.com/wu-clan) in [#820](https://github.com/fastapi-practices/fastapi_best_architecture/pull/820) -* Update the user agent in opera log model by [@wu-clan](https://github.com/wu-clan) in [#831](https://github.com/fastapi-practices/fastapi_best_architecture/pull/831) -* Fix code generation template missing Decimal by [@wu-clan](https://github.com/wu-clan) in [#830](https://github.com/fastapi-practices/fastapi_best_architecture/pull/830) -* Fix the get model object method return logic by [@wu-clan](https://github.com/wu-clan) in [#832](https://github.com/fastapi-practices/fastapi_best_architecture/pull/832) -* Simplify the code generation CLI parameters by [@wu-clan](https://github.com/wu-clan) in [#837](https://github.com/fastapi-practices/fastapi_best_architecture/pull/837) -* Add the refresh token to demo mode exclusion by [@wu-clan](https://github.com/wu-clan) in [#834](https://github.com/fastapi-practices/fastapi_best_architecture/pull/834) -* Add extension parameters to paging data by [@wu-clan](https://github.com/wu-clan) in [#835](https://github.com/fastapi-practices/fastapi_best_architecture/pull/835) -* Fix the phone filter in the user list by [@wu-clan](https://github.com/wu-clan) in [#838](https://github.com/fastapi-practices/fastapi_best_architecture/pull/838) -* Update docker and deployment script comments by [@wu-clan](https://github.com/wu-clan) in [#839](https://github.com/fastapi-practices/fastapi_best_architecture/pull/839) -* Update the version number to 1.8.2 by [@wu-clan](https://github.com/wu-clan) in [#840](https://github.com/fastapi-practices/fastapi_best_architecture/pull/840) +* Update changelog for v1.8.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#804](https://github.com/fastapi-practices/fastapi_best_architecture/pull/804) +* Update the swagger docs version to dynamic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#805](https://github.com/fastapi-practices/fastapi_best_architecture/pull/805) +* Fix the subprocess ensurepip got stuck in Linux by [@byte-voyager](https://github.com/byte-voyager) in [fastapi-practices/fastapi_best_architecture#806](https://github.com/fastapi-practices/fastapi_best_architecture/pull/806) +* Update the offline location information parse by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#807](https://github.com/fastapi-practices/fastapi_best_architecture/pull/807) +* Fix pgsql syntax error in code generation by [@byte-voyager](https://github.com/byte-voyager) in [fastapi-practices/fastapi_best_architecture#808](https://github.com/fastapi-practices/fastapi_best_architecture/pull/808) +* Fix summary default in opera log middleware by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#809](https://github.com/fastapi-practices/fastapi_best_architecture/pull/809) +* Fix special character password support in alembic by [@MortyZhaoy](https://github.com/MortyZhaoy) in [fastapi-practices/fastapi_best_architecture#811](https://github.com/fastapi-practices/fastapi_best_architecture/pull/811) +* Update the startup progress information display by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#812](https://github.com/fastapi-practices/fastapi_best_architecture/pull/812) +* Update the login captcha verify logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#815](https://github.com/fastapi-practices/fastapi_best_architecture/pull/815) +* Fix the summary in opera log middleware by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#816](https://github.com/fastapi-practices/fastapi_best_architecture/pull/816) +* Update the dict plugin table structure by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#817](https://github.com/fastapi-practices/fastapi_best_architecture/pull/817) +* Add version number output to the startup CLI by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#820](https://github.com/fastapi-practices/fastapi_best_architecture/pull/820) +* Update the user agent in opera log model by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#831](https://github.com/fastapi-practices/fastapi_best_architecture/pull/831) +* Fix code generation template missing Decimal by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#830](https://github.com/fastapi-practices/fastapi_best_architecture/pull/830) +* Fix the get model object method return logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#832](https://github.com/fastapi-practices/fastapi_best_architecture/pull/832) +* Simplify the code generation CLI parameters by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#837](https://github.com/fastapi-practices/fastapi_best_architecture/pull/837) +* Add the refresh token to demo mode exclusion by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#834](https://github.com/fastapi-practices/fastapi_best_architecture/pull/834) +* Add extension parameters to paging data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#835](https://github.com/fastapi-practices/fastapi_best_architecture/pull/835) +* Fix the phone filter in the user list by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#838](https://github.com/fastapi-practices/fastapi_best_architecture/pull/838) +* Update docker and deployment script comments by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#839](https://github.com/fastapi-practices/fastapi_best_architecture/pull/839) +* Update the version number to 1.8.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#840](https://github.com/fastapi-practices/fastapi_best_architecture/pull/840) ## New Contributors -* [@byte-voyager](https://github.com/byte-voyager) made their first contribution in [#806](https://github.com/fastapi-practices/fastapi_best_architecture/pull/806) -* [@MortyZhaoy](https://github.com/MortyZhaoy) made their first contribution in [#811](https://github.com/fastapi-practices/fastapi_best_architecture/pull/811) +* [@byte-voyager](https://github.com/byte-voyager) made their first contribution in [fastapi-practices/fastapi_best_architecture#806](https://github.com/fastapi-practices/fastapi_best_architecture/pull/806) +* [@MortyZhaoy](https://github.com/MortyZhaoy) made their first contribution in [fastapi-practices/fastapi_best_architecture#811](https://github.com/fastapi-practices/fastapi_best_architecture/pull/811) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.1...v1.8.2 @@ -310,43 +494,43 @@ -# [v1.8.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.8.1) - 2025-09-09 +# [v1.8.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.8.1) - 2025-09-09 ## What's Changed -* Update changelog for v1.8.0 by [@wu-clan](https://github.com/wu-clan) in [#772](https://github.com/fastapi-practices/fastapi_best_architecture/pull/772) -* Optimize the celery crontab expression validation by [@yanlingsishao](https://github.com/yanlingsishao) in [#773](https://github.com/fastapi-practices/fastapi_best_architecture/pull/773) -* Add bulk update interface for config plugin by [@wu-clan](https://github.com/wu-clan) in [#774](https://github.com/fastapi-practices/fastapi_best_architecture/pull/774) -* Optimize email sending config update logic by [@wu-clan](https://github.com/wu-clan) in [#775](https://github.com/fastapi-practices/fastapi_best_architecture/pull/775) -* Add test data SQL script for config plugin by [@wu-clan](https://github.com/wu-clan) in [#776](https://github.com/fastapi-practices/fastapi_best_architecture/pull/776) -* Update the env parameter value in env file by [@wu-clan](https://github.com/wu-clan) in [#777](https://github.com/fastapi-practices/fastapi_best_architecture/pull/777) -* Fix the docker compose env configuration file by [@wu-clan](https://github.com/wu-clan) in [#778](https://github.com/fastapi-practices/fastapi_best_architecture/pull/778) -* Fix redis client not close after plugin parse by [@wu-clan](https://github.com/wu-clan) in [#780](https://github.com/fastapi-practices/fastapi_best_architecture/pull/780) -* Fix the celery beat distributed lock timeout by [@wu-clan](https://github.com/wu-clan) in [#779](https://github.com/fastapi-practices/fastapi_best_architecture/pull/779) -* Fix the plugin model object detection logic by [@wuyao4](https://github.com/wuyao4) in [#782](https://github.com/fastapi-practices/fastapi_best_architecture/pull/782) -* Update the celery task result table creation logic by [@wu-clan](https://github.com/wu-clan) in [#783](https://github.com/fastapi-practices/fastapi_best_architecture/pull/783) -* Fix code generation template params and filename by [@wu-clan](https://github.com/wu-clan) in [#784](https://github.com/fastapi-practices/fastapi_best_architecture/pull/784) -* Fix CLI openapi url in production environment by [@siyue-wang](https://github.com/siyue-wang) in [#785](https://github.com/fastapi-practices/fastapi_best_architecture/pull/785) -* Update the model datetime column type to custom by [@wu-clan](https://github.com/wu-clan) in [#786](https://github.com/fastapi-practices/fastapi_best_architecture/pull/786) -* Update the opera log exception message record by [@wu-clan](https://github.com/wu-clan) in [#788](https://github.com/fastapi-practices/fastapi_best_architecture/pull/788) -* Update the handling of CORS 500 status code by [@wu-clan](https://github.com/wu-clan) in [#789](https://github.com/fastapi-practices/fastapi_best_architecture/pull/789) -* Add the Google OAuth2 login by [@wu-clan](https://github.com/wu-clan) in [#790](https://github.com/fastapi-practices/fastapi_best_architecture/pull/790) -* Fix the tzinfo comparison in the timezone type by [@IAseven](https://github.com/IAseven) in [#787](https://github.com/fastapi-practices/fastapi_best_architecture/pull/787) -* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#791](https://github.com/fastapi-practices/fastapi_best_architecture/pull/791) -* Update the notice model and pagination by [@wu-clan](https://github.com/wu-clan) in [#792](https://github.com/fastapi-practices/fastapi_best_architecture/pull/792) -* Add the dict type query all interface by [@wu-clan](https://github.com/wu-clan) in [#794](https://github.com/fastapi-practices/fastapi_best_architecture/pull/794) -* Fix sql script syntax error in config plugin by [@wu-clan](https://github.com/wu-clan) in [#793](https://github.com/fastapi-practices/fastapi_best_architecture/pull/793) -* Fix the psycopg dependency naming by [@wu-clan](https://github.com/wu-clan) in [#795](https://github.com/fastapi-practices/fastapi_best_architecture/pull/795) -* Add schema for the tree data by [@wu-clan](https://github.com/wu-clan) in [#796](https://github.com/fastapi-practices/fastapi_best_architecture/pull/796) -* Add the code generator to the CLI by [@wu-clan](https://github.com/wu-clan) in [#798](https://github.com/fastapi-practices/fastapi_best_architecture/pull/798) -* Optimize the code generation interaction logic by [@wu-clan](https://github.com/wu-clan) in [#799](https://github.com/fastapi-practices/fastapi_best_architecture/pull/799) -* Fix console import in main module by [@wu-clan](https://github.com/wu-clan) in [#800](https://github.com/fastapi-practices/fastapi_best_architecture/pull/800) -* Fix the subprocess check call failed in windows by [@yzbf-lin](https://github.com/yzbf-lin) in [#802](https://github.com/fastapi-practices/fastapi_best_architecture/pull/802) -* Fix alembic migration file missing import by [@wu-clan](https://github.com/wu-clan) in [#803](https://github.com/fastapi-practices/fastapi_best_architecture/pull/803) +* Update changelog for v1.8.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#772](https://github.com/fastapi-practices/fastapi_best_architecture/pull/772) +* Optimize the celery crontab expression validation by [@yanlingsishao](https://github.com/yanlingsishao) in [fastapi-practices/fastapi_best_architecture#773](https://github.com/fastapi-practices/fastapi_best_architecture/pull/773) +* Add bulk update interface for config plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#774](https://github.com/fastapi-practices/fastapi_best_architecture/pull/774) +* Optimize email sending config update logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#775](https://github.com/fastapi-practices/fastapi_best_architecture/pull/775) +* Add test data SQL script for config plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#776](https://github.com/fastapi-practices/fastapi_best_architecture/pull/776) +* Update the env parameter value in env file by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#777](https://github.com/fastapi-practices/fastapi_best_architecture/pull/777) +* Fix the docker compose env configuration file by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#778](https://github.com/fastapi-practices/fastapi_best_architecture/pull/778) +* Fix redis client not close after plugin parse by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#780](https://github.com/fastapi-practices/fastapi_best_architecture/pull/780) +* Fix the celery beat distributed lock timeout by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#779](https://github.com/fastapi-practices/fastapi_best_architecture/pull/779) +* Fix the plugin model object detection logic by [@wuyao4](https://github.com/wuyao4) in [fastapi-practices/fastapi_best_architecture#782](https://github.com/fastapi-practices/fastapi_best_architecture/pull/782) +* Update the celery task result table creation logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#783](https://github.com/fastapi-practices/fastapi_best_architecture/pull/783) +* Fix code generation template params and filename by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#784](https://github.com/fastapi-practices/fastapi_best_architecture/pull/784) +* Fix CLI openapi url in production environment by [@siyue-wang](https://github.com/siyue-wang) in [fastapi-practices/fastapi_best_architecture#785](https://github.com/fastapi-practices/fastapi_best_architecture/pull/785) +* Update the model datetime column type to custom by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#786](https://github.com/fastapi-practices/fastapi_best_architecture/pull/786) +* Update the opera log exception message record by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#788](https://github.com/fastapi-practices/fastapi_best_architecture/pull/788) +* Update the handling of CORS 500 status code by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#789](https://github.com/fastapi-practices/fastapi_best_architecture/pull/789) +* Add the Google OAuth2 login by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#790](https://github.com/fastapi-practices/fastapi_best_architecture/pull/790) +* Fix the tzinfo comparison in the timezone type by [@IAseven](https://github.com/IAseven) in [fastapi-practices/fastapi_best_architecture#787](https://github.com/fastapi-practices/fastapi_best_architecture/pull/787) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#791](https://github.com/fastapi-practices/fastapi_best_architecture/pull/791) +* Update the notice model and pagination by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#792](https://github.com/fastapi-practices/fastapi_best_architecture/pull/792) +* Add the dict type query all interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#794](https://github.com/fastapi-practices/fastapi_best_architecture/pull/794) +* Fix sql script syntax error in config plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#793](https://github.com/fastapi-practices/fastapi_best_architecture/pull/793) +* Fix the psycopg dependency naming by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#795](https://github.com/fastapi-practices/fastapi_best_architecture/pull/795) +* Add schema for the tree data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#796](https://github.com/fastapi-practices/fastapi_best_architecture/pull/796) +* Add the code generator to the CLI by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#798](https://github.com/fastapi-practices/fastapi_best_architecture/pull/798) +* Optimize the code generation interaction logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#799](https://github.com/fastapi-practices/fastapi_best_architecture/pull/799) +* Fix console import in main module by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#800](https://github.com/fastapi-practices/fastapi_best_architecture/pull/800) +* Fix the subprocess check call failed in windows by [@yzbf-lin](https://github.com/yzbf-lin) in [fastapi-practices/fastapi_best_architecture#802](https://github.com/fastapi-practices/fastapi_best_architecture/pull/802) +* Fix alembic migration file missing import by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#803](https://github.com/fastapi-practices/fastapi_best_architecture/pull/803) ## New Contributors -* [@yanlingsishao](https://github.com/yanlingsishao) made their first contribution in [#773](https://github.com/fastapi-practices/fastapi_best_architecture/pull/773) -* [@wuyao4](https://github.com/wuyao4) made their first contribution in [#782](https://github.com/fastapi-practices/fastapi_best_architecture/pull/782) -* [@siyue-wang](https://github.com/siyue-wang) made their first contribution in [#785](https://github.com/fastapi-practices/fastapi_best_architecture/pull/785) +* [@yanlingsishao](https://github.com/yanlingsishao) made their first contribution in [fastapi-practices/fastapi_best_architecture#773](https://github.com/fastapi-practices/fastapi_best_architecture/pull/773) +* [@wuyao4](https://github.com/wuyao4) made their first contribution in [fastapi-practices/fastapi_best_architecture#782](https://github.com/fastapi-practices/fastapi_best_architecture/pull/782) +* [@siyue-wang](https://github.com/siyue-wang) made their first contribution in [fastapi-practices/fastapi_best_architecture#785](https://github.com/fastapi-practices/fastapi_best_architecture/pull/785) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.0...v1.8.1 @@ -363,43 +547,43 @@ -# [v1.8.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.8.0) - 2025-08-15 +# [v1.8.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.8.0) - 2025-08-15 ## What's Changed -* Update changelog for v1.7.0 by [@wu-clan](https://github.com/wu-clan) in [#729](https://github.com/fastapi-practices/fastapi_best_architecture/pull/729) -* Simplify task crontab expression validation by [@wu-clan](https://github.com/wu-clan) in [#733](https://github.com/fastapi-practices/fastapi_best_architecture/pull/733) -* Add distributed lock for scheduled task by [@wu-clan](https://github.com/wu-clan) in [#732](https://github.com/fastapi-practices/fastapi_best_architecture/pull/732) -* Update the default cache period for userinfo by [@wu-clan](https://github.com/wu-clan) in [#734](https://github.com/fastapi-practices/fastapi_best_architecture/pull/734) -* Fix celery CLI option to required by [@wu-clan](https://github.com/wu-clan) in [#737](https://github.com/fastapi-practices/fastapi_best_architecture/pull/737) -* Add auth whitelist regular expression config by [@wu-clan](https://github.com/wu-clan) in [#738](https://github.com/fastapi-practices/fastapi_best_architecture/pull/738) -* Fix the opera log field encryption by [@wu-clan](https://github.com/wu-clan) in [#739](https://github.com/fastapi-practices/fastapi_best_architecture/pull/739) -* Update the OAuth2 login password policy by [@wu-clan](https://github.com/wu-clan) in [#741](https://github.com/fastapi-practices/fastapi_best_architecture/pull/741) -* Add update support for user email and phone by [@wu-clan](https://github.com/wu-clan) in [#742](https://github.com/fastapi-practices/fastapi_best_architecture/pull/742) -* Fix the error trigger when model auto import by [@wu-clan](https://github.com/wu-clan) in [#743](https://github.com/fastapi-practices/fastapi_best_architecture/pull/743) -* Simplify the plugin status update logic by [@wu-clan](https://github.com/wu-clan) in [#744](https://github.com/fastapi-practices/fastapi_best_architecture/pull/744) -* Add some interfaces for user profiles by [@wu-clan](https://github.com/wu-clan) in [#745](https://github.com/fastapi-practices/fastapi_best_architecture/pull/745) -* Add schedule task demo that contains params by [@wu-clan](https://github.com/wu-clan) in [#746](https://github.com/fastapi-practices/fastapi_best_architecture/pull/746) -* Fix the kwargs params of schedule task by [@wu-clan](https://github.com/wu-clan) in [#747](https://github.com/fastapi-practices/fastapi_best_architecture/pull/747) -* Refactor code generation files and routes by [@wu-clan](https://github.com/wu-clan) in [#748](https://github.com/fastapi-practices/fastapi_best_architecture/pull/748) -* Refactor task routes and add control routes by [@wu-clan](https://github.com/wu-clan) in [#749](https://github.com/fastapi-practices/fastapi_best_architecture/pull/749) -* Fix message format in validation exception handler by [@wu-clan](https://github.com/wu-clan) in [#755](https://github.com/fastapi-practices/fastapi_best_architecture/pull/755) -* Update the opera log desensitization method by [@wu-clan](https://github.com/wu-clan) in [#756](https://github.com/fastapi-practices/fastapi_best_architecture/pull/756) -* Add business pagination in the code generator by [@wu-clan](https://github.com/wu-clan) in [#757](https://github.com/fastapi-practices/fastapi_best_architecture/pull/757) -* Optimize the data sort logic of tree nodes by [@wu-clan](https://github.com/wu-clan) in [#758](https://github.com/fastapi-practices/fastapi_best_architecture/pull/758) -* Update log output config and format by [@wu-clan](https://github.com/wu-clan) in [#759](https://github.com/fastapi-practices/fastapi_best_architecture/pull/759) -* Update the naming of table creation function by [@wu-clan](https://github.com/wu-clan) in [#760](https://github.com/fastapi-practices/fastapi_best_architecture/pull/760) -* Optimize the opera log storage logic through queue by [@IAseven](https://github.com/IAseven) in [#750](https://github.com/fastapi-practices/fastapi_best_architecture/pull/750) -* Optimize naming and preview in code generation by [@wu-clan](https://github.com/wu-clan) in [#764](https://github.com/fastapi-practices/fastapi_best_architecture/pull/764) -* Update the description for the run file by [@wu-clan](https://github.com/wu-clan) in [#766](https://github.com/fastapi-practices/fastapi_best_architecture/pull/766) -* Optimize the timezone datetime return encoder by [@wu-clan](https://github.com/wu-clan) in [#767](https://github.com/fastapi-practices/fastapi_best_architecture/pull/767) -* Update the content layout of the config file by [@wu-clan](https://github.com/wu-clan) in [#768](https://github.com/fastapi-practices/fastapi_best_architecture/pull/768) -* Add a standalone email sending plugin by [@wu-clan](https://github.com/wu-clan) in [#769](https://github.com/fastapi-practices/fastapi_best_architecture/pull/769) -* Add i18n support for response message by [@downdawn](https://github.com/downdawn) in [#753](https://github.com/fastapi-practices/fastapi_best_architecture/pull/753) -* Update the menu title in SQL scripts by [@wu-clan](https://github.com/wu-clan) in [#770](https://github.com/fastapi-practices/fastapi_best_architecture/pull/770) -* Update the version number to 1.8.0 by [@wu-clan](https://github.com/wu-clan) in [#771](https://github.com/fastapi-practices/fastapi_best_architecture/pull/771) +* Update changelog for v1.7.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#729](https://github.com/fastapi-practices/fastapi_best_architecture/pull/729) +* Simplify task crontab expression validation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#733](https://github.com/fastapi-practices/fastapi_best_architecture/pull/733) +* Add distributed lock for scheduled task by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#732](https://github.com/fastapi-practices/fastapi_best_architecture/pull/732) +* Update the default cache period for userinfo by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#734](https://github.com/fastapi-practices/fastapi_best_architecture/pull/734) +* Fix celery CLI option to required by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#737](https://github.com/fastapi-practices/fastapi_best_architecture/pull/737) +* Add auth whitelist regular expression config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#738](https://github.com/fastapi-practices/fastapi_best_architecture/pull/738) +* Fix the opera log field encryption by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#739](https://github.com/fastapi-practices/fastapi_best_architecture/pull/739) +* Update the OAuth2 login password policy by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#741](https://github.com/fastapi-practices/fastapi_best_architecture/pull/741) +* Add update support for user email and phone by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#742](https://github.com/fastapi-practices/fastapi_best_architecture/pull/742) +* Fix the error trigger when model auto import by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#743](https://github.com/fastapi-practices/fastapi_best_architecture/pull/743) +* Simplify the plugin status update logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#744](https://github.com/fastapi-practices/fastapi_best_architecture/pull/744) +* Add some interfaces for user profiles by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#745](https://github.com/fastapi-practices/fastapi_best_architecture/pull/745) +* Add schedule task demo that contains params by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#746](https://github.com/fastapi-practices/fastapi_best_architecture/pull/746) +* Fix the kwargs params of schedule task by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#747](https://github.com/fastapi-practices/fastapi_best_architecture/pull/747) +* Refactor code generation files and routes by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#748](https://github.com/fastapi-practices/fastapi_best_architecture/pull/748) +* Refactor task routes and add control routes by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#749](https://github.com/fastapi-practices/fastapi_best_architecture/pull/749) +* Fix message format in validation exception handler by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#755](https://github.com/fastapi-practices/fastapi_best_architecture/pull/755) +* Update the opera log desensitization method by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#756](https://github.com/fastapi-practices/fastapi_best_architecture/pull/756) +* Add business pagination in the code generator by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#757](https://github.com/fastapi-practices/fastapi_best_architecture/pull/757) +* Optimize the data sort logic of tree nodes by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#758](https://github.com/fastapi-practices/fastapi_best_architecture/pull/758) +* Update log output config and format by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#759](https://github.com/fastapi-practices/fastapi_best_architecture/pull/759) +* Update the naming of table creation function by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#760](https://github.com/fastapi-practices/fastapi_best_architecture/pull/760) +* Optimize the opera log storage logic through queue by [@IAseven](https://github.com/IAseven) in [fastapi-practices/fastapi_best_architecture#750](https://github.com/fastapi-practices/fastapi_best_architecture/pull/750) +* Optimize naming and preview in code generation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#764](https://github.com/fastapi-practices/fastapi_best_architecture/pull/764) +* Update the description for the run file by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#766](https://github.com/fastapi-practices/fastapi_best_architecture/pull/766) +* Optimize the timezone datetime return encoder by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#767](https://github.com/fastapi-practices/fastapi_best_architecture/pull/767) +* Update the content layout of the config file by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#768](https://github.com/fastapi-practices/fastapi_best_architecture/pull/768) +* Add a standalone email sending plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#769](https://github.com/fastapi-practices/fastapi_best_architecture/pull/769) +* Add i18n support for response message by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#753](https://github.com/fastapi-practices/fastapi_best_architecture/pull/753) +* Update the menu title in SQL scripts by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#770](https://github.com/fastapi-practices/fastapi_best_architecture/pull/770) +* Update the version number to 1.8.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#771](https://github.com/fastapi-practices/fastapi_best_architecture/pull/771) ## New Contributors -* [@IAseven](https://github.com/IAseven) made their first contribution in [#750](https://github.com/fastapi-practices/fastapi_best_architecture/pull/750) +* [@IAseven](https://github.com/IAseven) made their first contribution in [fastapi-practices/fastapi_best_architecture#750](https://github.com/fastapi-practices/fastapi_best_architecture/pull/750) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.7.0...v1.8.0 @@ -413,33 +597,33 @@ -# [v1.7.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.7.0) - 2025-07-16 +# [v1.7.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.7.0) - 2025-07-16 ## What's Changed -* Update the changelog for v1.6.0 by [@wu-clan](https://github.com/wu-clan) in [#703](https://github.com/fastapi-practices/fastapi_best_architecture/pull/703) -* Update the CLI to be executed async by [@wu-clan](https://github.com/wu-clan) in [#704](https://github.com/fastapi-practices/fastapi_best_architecture/pull/704) -* Fix the code generation schema template by [@wu-clan](https://github.com/wu-clan) in [#706](https://github.com/fastapi-practices/fastapi_best_architecture/pull/706) -* Replace gunicorn deployment to granian by [@wu-clan](https://github.com/wu-clan) in [#705](https://github.com/fastapi-practices/fastapi_best_architecture/pull/705) -* Fix the code generation delete schema template by [@wu-clan](https://github.com/wu-clan) in [#708](https://github.com/fastapi-practices/fastapi_best_architecture/pull/708) -* Update the refresh token verify mechanism by [@wu-clan](https://github.com/wu-clan) in [#710](https://github.com/fastapi-practices/fastapi_best_architecture/pull/710) -* Update the reload excludes for CLI run by [@wu-clan](https://github.com/wu-clan) in [#709](https://github.com/fastapi-practices/fastapi_best_architecture/pull/709) -* Add CLI support for execute sql scripts by [@wu-clan](https://github.com/wu-clan) in [#711](https://github.com/fastapi-practices/fastapi_best_architecture/pull/711) -* Update the granian env to command params by [@wu-clan](https://github.com/wu-clan) in [#712](https://github.com/fastapi-practices/fastapi_best_architecture/pull/712) -* Update the middleware logging accuracy by [@wu-clan](https://github.com/wu-clan) in [#713](https://github.com/fastapi-practices/fastapi_best_architecture/pull/713) -* Update the log output default style by [@wu-clan](https://github.com/wu-clan) in [#714](https://github.com/fastapi-practices/fastapi_best_architecture/pull/714) -* Optimize the analysis of get plugins by [@wu-clan](https://github.com/wu-clan) in [#716](https://github.com/fastapi-practices/fastapi_best_architecture/pull/716) -* Simplify user permission database queries by [@wu-clan](https://github.com/wu-clan) in [#717](https://github.com/fastapi-practices/fastapi_best_architecture/pull/717) -* Update the CLI startup service mode by [@wu-clan](https://github.com/wu-clan) in [#718](https://github.com/fastapi-practices/fastapi_best_architecture/pull/718) -* Add support for celery dynamic tasks by [@wu-clan](https://github.com/wu-clan) in [#715](https://github.com/fastapi-practices/fastapi_best_architecture/pull/715) -* Fix the celery task scheduler query by [@wu-clan](https://github.com/wu-clan) in [#719](https://github.com/fastapi-practices/fastapi_best_architecture/pull/719) -* Update the celery task comment and name by [@wu-clan](https://github.com/wu-clan) in [#720](https://github.com/fastapi-practices/fastapi_best_architecture/pull/720) -* Optimize celery integrations and events by [@wu-clan](https://github.com/wu-clan) in [#721](https://github.com/fastapi-practices/fastapi_best_architecture/pull/721) -* Simplify celery task crontab config by [@wu-clan](https://github.com/wu-clan) in [#722](https://github.com/fastapi-practices/fastapi_best_architecture/pull/722) -* Delete the default value of schema enum data by [@wu-clan](https://github.com/wu-clan) in [#723](https://github.com/fastapi-practices/fastapi_best_architecture/pull/723) -* Fix the parsing of execution task params by [@wu-clan](https://github.com/wu-clan) in [#725](https://github.com/fastapi-practices/fastapi_best_architecture/pull/725) -* Bump granian from 2.4.0 to 2.4.2 by [@wu-clan](https://github.com/wu-clan) in [#727](https://github.com/fastapi-practices/fastapi_best_architecture/pull/727) -* Add CLI support for startup celery services by [@wu-clan](https://github.com/wu-clan) in [#724](https://github.com/fastapi-practices/fastapi_best_architecture/pull/724) -* Fix login and operation log clearing by [@wu-clan](https://github.com/wu-clan) in [#728](https://github.com/fastapi-practices/fastapi_best_architecture/pull/728) +* Update the changelog for v1.6.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#703](https://github.com/fastapi-practices/fastapi_best_architecture/pull/703) +* Update the CLI to be executed async by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#704](https://github.com/fastapi-practices/fastapi_best_architecture/pull/704) +* Fix the code generation schema template by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#706](https://github.com/fastapi-practices/fastapi_best_architecture/pull/706) +* Replace gunicorn deployment to granian by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#705](https://github.com/fastapi-practices/fastapi_best_architecture/pull/705) +* Fix the code generation delete schema template by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#708](https://github.com/fastapi-practices/fastapi_best_architecture/pull/708) +* Update the refresh token verify mechanism by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#710](https://github.com/fastapi-practices/fastapi_best_architecture/pull/710) +* Update the reload excludes for CLI run by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#709](https://github.com/fastapi-practices/fastapi_best_architecture/pull/709) +* Add CLI support for execute sql scripts by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#711](https://github.com/fastapi-practices/fastapi_best_architecture/pull/711) +* Update the granian env to command params by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#712](https://github.com/fastapi-practices/fastapi_best_architecture/pull/712) +* Update the middleware logging accuracy by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#713](https://github.com/fastapi-practices/fastapi_best_architecture/pull/713) +* Update the log output default style by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#714](https://github.com/fastapi-practices/fastapi_best_architecture/pull/714) +* Optimize the analysis of get plugins by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#716](https://github.com/fastapi-practices/fastapi_best_architecture/pull/716) +* Simplify user permission database queries by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#717](https://github.com/fastapi-practices/fastapi_best_architecture/pull/717) +* Update the CLI startup service mode by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#718](https://github.com/fastapi-practices/fastapi_best_architecture/pull/718) +* Add support for celery dynamic tasks by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#715](https://github.com/fastapi-practices/fastapi_best_architecture/pull/715) +* Fix the celery task scheduler query by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#719](https://github.com/fastapi-practices/fastapi_best_architecture/pull/719) +* Update the celery task comment and name by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#720](https://github.com/fastapi-practices/fastapi_best_architecture/pull/720) +* Optimize celery integrations and events by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#721](https://github.com/fastapi-practices/fastapi_best_architecture/pull/721) +* Simplify celery task crontab config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#722](https://github.com/fastapi-practices/fastapi_best_architecture/pull/722) +* Delete the default value of schema enum data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#723](https://github.com/fastapi-practices/fastapi_best_architecture/pull/723) +* Fix the parsing of execution task params by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#725](https://github.com/fastapi-practices/fastapi_best_architecture/pull/725) +* Bump granian from 2.4.0 to 2.4.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#727](https://github.com/fastapi-practices/fastapi_best_architecture/pull/727) +* Add CLI support for startup celery services by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#724](https://github.com/fastapi-practices/fastapi_best_architecture/pull/724) +* Fix login and operation log clearing by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#728](https://github.com/fastapi-practices/fastapi_best_architecture/pull/728) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.6.0...v1.7.0 @@ -452,21 +636,21 @@ -# [v1.6.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.6.0) - 2025-06-30 +# [v1.6.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.6.0) - 2025-06-30 ## What's Changed -* Update changelog for v1.5.2 by [@wu-clan](https://github.com/wu-clan) in [#690](https://github.com/fastapi-practices/fastapi_best_architecture/pull/690) -* Optimize dict create and update logic by [@wu-clan](https://github.com/wu-clan) in [#691](https://github.com/fastapi-practices/fastapi_best_architecture/pull/691) -* Fix the OAuth2 redirect route names by [@wu-clan](https://github.com/wu-clan) in [#693](https://github.com/fastapi-practices/fastapi_best_architecture/pull/693) -* Update the SQL to adapt frontend plugin by [@wu-clan](https://github.com/wu-clan) in [#694](https://github.com/fastapi-practices/fastapi_best_architecture/pull/694) -* Update the extension plugin config by [@wu-clan](https://github.com/wu-clan) in [#695](https://github.com/fastapi-practices/fastapi_best_architecture/pull/695) -* Add the test user to SQL scripts by [@wu-clan](https://github.com/wu-clan) in [#696](https://github.com/fastapi-practices/fastapi_best_architecture/pull/696) -* Add custom CLI for service startup by [@wu-clan](https://github.com/wu-clan) in [#697](https://github.com/fastapi-practices/fastapi_best_architecture/pull/697) -* Add CLI support for plugin install by [@wu-clan](https://github.com/wu-clan) in [#698](https://github.com/fastapi-practices/fastapi_best_architecture/pull/698) -* Update the help for CLI run worker by [@wu-clan](https://github.com/wu-clan) in [#699](https://github.com/fastapi-practices/fastapi_best_architecture/pull/699) -* Optimize the installation of plugin dependencies by [@wu-clan](https://github.com/wu-clan) in [#700](https://github.com/fastapi-practices/fastapi_best_architecture/pull/700) -* Update the Dockerfile to adapt latest code by [@wu-clan](https://github.com/wu-clan) in [#701](https://github.com/fastapi-practices/fastapi_best_architecture/pull/701) -* Update the version number to 1.6.0 by [@wu-clan](https://github.com/wu-clan) in [#702](https://github.com/fastapi-practices/fastapi_best_architecture/pull/702) +* Update changelog for v1.5.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#690](https://github.com/fastapi-practices/fastapi_best_architecture/pull/690) +* Optimize dict create and update logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#691](https://github.com/fastapi-practices/fastapi_best_architecture/pull/691) +* Fix the OAuth2 redirect route names by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#693](https://github.com/fastapi-practices/fastapi_best_architecture/pull/693) +* Update the SQL to adapt frontend plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#694](https://github.com/fastapi-practices/fastapi_best_architecture/pull/694) +* Update the extension plugin config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#695](https://github.com/fastapi-practices/fastapi_best_architecture/pull/695) +* Add the test user to SQL scripts by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#696](https://github.com/fastapi-practices/fastapi_best_architecture/pull/696) +* Add custom CLI for service startup by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#697](https://github.com/fastapi-practices/fastapi_best_architecture/pull/697) +* Add CLI support for plugin install by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#698](https://github.com/fastapi-practices/fastapi_best_architecture/pull/698) +* Update the help for CLI run worker by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#699](https://github.com/fastapi-practices/fastapi_best_architecture/pull/699) +* Optimize the installation of plugin dependencies by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#700](https://github.com/fastapi-practices/fastapi_best_architecture/pull/700) +* Update the Dockerfile to adapt latest code by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#701](https://github.com/fastapi-practices/fastapi_best_architecture/pull/701) +* Update the version number to 1.6.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#702](https://github.com/fastapi-practices/fastapi_best_architecture/pull/702) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.2...v1.6.0 @@ -479,24 +663,24 @@ -# [v1.5.2](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.5.2) - 2025-06-24 +# [v1.5.2](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.5.2) - 2025-06-24 ## What's Changed -* Update changelog for v1.5.1 by [@wu-clan](https://github.com/wu-clan) in [#671](https://github.com/fastapi-practices/fastapi_best_architecture/pull/671) -* Fix some error class import by [@wu-clan](https://github.com/wu-clan) in [#672](https://github.com/fastapi-practices/fastapi_best_architecture/pull/672) -* Optimize routes to better align with RESTful by [@wu-clan](https://github.com/wu-clan) in [#673](https://github.com/fastapi-practices/fastapi_best_architecture/pull/673) -* Add the snowflake ID sql script by [@wu-clan](https://github.com/wu-clan) in [#675](https://github.com/fastapi-practices/fastapi_best_architecture/pull/675) -* Optimize token detection and caching logic by [@wu-clan](https://github.com/wu-clan) in [#677](https://github.com/fastapi-practices/fastapi_best_architecture/pull/677) -* Update cache cleanup for logout interface by [@wu-clan](https://github.com/wu-clan) in [#678](https://github.com/fastapi-practices/fastapi_best_architecture/pull/678) -* Add dictionary type and datas queries by [@wu-clan](https://github.com/wu-clan) in [#679](https://github.com/fastapi-practices/fastapi_best_architecture/pull/679) -* Optimize api with semantic HTTP status codes by [@downdawn](https://github.com/downdawn) in [#681](https://github.com/fastapi-practices/fastapi_best_architecture/pull/681) -* Fix the code with outdated system config by [@wu-clan](https://github.com/wu-clan) in [#683](https://github.com/fastapi-practices/fastapi_best_architecture/pull/683) -* Update dict data label column config by [@wu-clan](https://github.com/wu-clan) in [#684](https://github.com/fastapi-practices/fastapi_best_architecture/pull/684) -* Update the init test data for SQL scripts by [@wu-clan](https://github.com/wu-clan) in [#685](https://github.com/fastapi-practices/fastapi_best_architecture/pull/685) -* Simplify custom response status codes by [@wu-clan](https://github.com/wu-clan) in [#686](https://github.com/fastapi-practices/fastapi_best_architecture/pull/686) -* Optimize the zip plug-in file name parsing by [@wu-clan](https://github.com/wu-clan) in [#687](https://github.com/fastapi-practices/fastapi_best_architecture/pull/687) -* Add built-in plugin missing files by [@wu-clan](https://github.com/wu-clan) in [#688](https://github.com/fastapi-practices/fastapi_best_architecture/pull/688) -* Update the dict pagination query parameters by [@wu-clan](https://github.com/wu-clan) in [#689](https://github.com/fastapi-practices/fastapi_best_architecture/pull/689) +* Update changelog for v1.5.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#671](https://github.com/fastapi-practices/fastapi_best_architecture/pull/671) +* Fix some error class import by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#672](https://github.com/fastapi-practices/fastapi_best_architecture/pull/672) +* Optimize routes to better align with RESTful by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#673](https://github.com/fastapi-practices/fastapi_best_architecture/pull/673) +* Add the snowflake ID sql script by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#675](https://github.com/fastapi-practices/fastapi_best_architecture/pull/675) +* Optimize token detection and caching logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#677](https://github.com/fastapi-practices/fastapi_best_architecture/pull/677) +* Update cache cleanup for logout interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#678](https://github.com/fastapi-practices/fastapi_best_architecture/pull/678) +* Add dictionary type and datas queries by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#679](https://github.com/fastapi-practices/fastapi_best_architecture/pull/679) +* Optimize api with semantic HTTP status codes by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#681](https://github.com/fastapi-practices/fastapi_best_architecture/pull/681) +* Fix the code with outdated system config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#683](https://github.com/fastapi-practices/fastapi_best_architecture/pull/683) +* Update dict data label column config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#684](https://github.com/fastapi-practices/fastapi_best_architecture/pull/684) +* Update the init test data for SQL scripts by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#685](https://github.com/fastapi-practices/fastapi_best_architecture/pull/685) +* Simplify custom response status codes by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#686](https://github.com/fastapi-practices/fastapi_best_architecture/pull/686) +* Optimize the zip plug-in file name parsing by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#687](https://github.com/fastapi-practices/fastapi_best_architecture/pull/687) +* Add built-in plugin missing files by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#688](https://github.com/fastapi-practices/fastapi_best_architecture/pull/688) +* Update the dict pagination query parameters by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#689](https://github.com/fastapi-practices/fastapi_best_architecture/pull/689) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.1...v1.5.2 @@ -510,14 +694,14 @@ -# [v1.5.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.5.1) - 2025-06-16 +# [v1.5.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.5.1) - 2025-06-16 ## What's Changed -* Update changelog for v1.5.0 by [@wu-clan](https://github.com/wu-clan) in [#664](https://github.com/fastapi-practices/fastapi_best_architecture/pull/664) -* Fix the sidebar menu type filtering by [@wu-clan](https://github.com/wu-clan) in [#667](https://github.com/fastapi-practices/fastapi_best_architecture/pull/667) -* Bump sqlalchemy crud plus version to 1.10.0 by [@wu-clan](https://github.com/wu-clan) in [#668](https://github.com/fastapi-practices/fastapi_best_architecture/pull/668) -* Fix the postgresql sql script syntax error by [@downdawn](https://github.com/downdawn) in [#669](https://github.com/fastapi-practices/fastapi_best_architecture/pull/669) -* Add Initial Snowflake ID Support by [@downdawn](https://github.com/downdawn) in [#670](https://github.com/fastapi-practices/fastapi_best_architecture/pull/670) +* Update changelog for v1.5.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#664](https://github.com/fastapi-practices/fastapi_best_architecture/pull/664) +* Fix the sidebar menu type filtering by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#667](https://github.com/fastapi-practices/fastapi_best_architecture/pull/667) +* Bump sqlalchemy crud plus version to 1.10.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#668](https://github.com/fastapi-practices/fastapi_best_architecture/pull/668) +* Fix the postgresql sql script syntax error by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#669](https://github.com/fastapi-practices/fastapi_best_architecture/pull/669) +* Add Initial Snowflake ID Support by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#670](https://github.com/fastapi-practices/fastapi_best_architecture/pull/670) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.0...v1.5.1 @@ -531,21 +715,21 @@ -# [v1.5.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.5.0) - 2025-06-09 +# [v1.5.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.5.0) - 2025-06-09 ## What's Changed -* Update changelog for v1.4.3 by [@wu-clan](https://github.com/wu-clan) in [#651](https://github.com/fastapi-practices/fastapi_best_architecture/pull/651) -* Update OAuth2 callback interface return by [@wu-clan](https://github.com/wu-clan) in [#653](https://github.com/fastapi-practices/fastapi_best_architecture/pull/653) -* Update user email and phone operation logic by [@wu-clan](https://github.com/wu-clan) in [#654](https://github.com/fastapi-practices/fastapi_best_architecture/pull/654) -* Simplify OAuth2 model and optimize auth service by [@wu-clan](https://github.com/wu-clan) in [#655](https://github.com/fastapi-practices/fastapi_best_architecture/pull/655) -* Add OAuth2 user to auto bind a role by [@wu-clan](https://github.com/wu-clan) in [#656](https://github.com/fastapi-practices/fastapi_best_architecture/pull/656) -* Update data scope and rule to m2m by [@wu-clan](https://github.com/wu-clan) in [#657](https://github.com/fastapi-practices/fastapi_best_architecture/pull/657) -* Update code generate interface permission by [@wu-clan](https://github.com/wu-clan) in [#658](https://github.com/fastapi-practices/fastapi_best_architecture/pull/658) -* Update the plugin download interface permission by [@wu-clan](https://github.com/wu-clan) in [#659](https://github.com/fastapi-practices/fastapi_best_architecture/pull/659) -* Update auth failed default status code by [@wu-clan](https://github.com/wu-clan) in [#660](https://github.com/fastapi-practices/fastapi_best_architecture/pull/660) -* Update menu sort in init test sql by [@wu-clan](https://github.com/wu-clan) in [#661](https://github.com/fastapi-practices/fastapi_best_architecture/pull/661) -* Add data permission in init test sql by [@wu-clan](https://github.com/wu-clan) in [#662](https://github.com/fastapi-practices/fastapi_best_architecture/pull/662) -* Update the version to 1.5.0 by [@wu-clan](https://github.com/wu-clan) in [#663](https://github.com/fastapi-practices/fastapi_best_architecture/pull/663) +* Update changelog for v1.4.3 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#651](https://github.com/fastapi-practices/fastapi_best_architecture/pull/651) +* Update OAuth2 callback interface return by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#653](https://github.com/fastapi-practices/fastapi_best_architecture/pull/653) +* Update user email and phone operation logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#654](https://github.com/fastapi-practices/fastapi_best_architecture/pull/654) +* Simplify OAuth2 model and optimize auth service by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#655](https://github.com/fastapi-practices/fastapi_best_architecture/pull/655) +* Add OAuth2 user to auto bind a role by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#656](https://github.com/fastapi-practices/fastapi_best_architecture/pull/656) +* Update data scope and rule to m2m by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#657](https://github.com/fastapi-practices/fastapi_best_architecture/pull/657) +* Update code generate interface permission by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#658](https://github.com/fastapi-practices/fastapi_best_architecture/pull/658) +* Update the plugin download interface permission by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#659](https://github.com/fastapi-practices/fastapi_best_architecture/pull/659) +* Update auth failed default status code by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#660](https://github.com/fastapi-practices/fastapi_best_architecture/pull/660) +* Update menu sort in init test sql by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#661](https://github.com/fastapi-practices/fastapi_best_architecture/pull/661) +* Add data permission in init test sql by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#662](https://github.com/fastapi-practices/fastapi_best_architecture/pull/662) +* Update the version to 1.5.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#663](https://github.com/fastapi-practices/fastapi_best_architecture/pull/663) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.3...v1.5.0 @@ -558,19 +742,19 @@ -# [v1.4.3](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.4.3) - 2025-06-02 +# [v1.4.3](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.4.3) - 2025-06-02 ## What's Changed -* Update changelog for v1.4.2 by [@wu-clan](https://github.com/wu-clan) in [#639](https://github.com/fastapi-practices/fastapi_best_architecture/pull/639) -* Fix the role update business variables by [@wu-clan](https://github.com/wu-clan) in [#640](https://github.com/fastapi-practices/fastapi_best_architecture/pull/640) -* Fix the menu delete interface arg description by [@wu-clan](https://github.com/wu-clan) in [#641](https://github.com/fastapi-practices/fastapi_best_architecture/pull/641) -* Fix the filter of query all menus by [@wu-clan](https://github.com/wu-clan) in [#642](https://github.com/fastapi-practices/fastapi_best_architecture/pull/642) -* Refactor routes to better align with RESTful by [@wu-clan](https://github.com/wu-clan) in [#645](https://github.com/fastapi-practices/fastapi_best_architecture/pull/645) -* Update the server startup time to string by [@wu-clan](https://github.com/wu-clan) in [#646](https://github.com/fastapi-practices/fastapi_best_architecture/pull/646) -* Add get all data scope rules interface by [@wu-clan](https://github.com/wu-clan) in [#647](https://github.com/fastapi-practices/fastapi_best_architecture/pull/647) -* Add data permission condition for filter data by [@wu-clan](https://github.com/wu-clan) in [#648](https://github.com/fastapi-practices/fastapi_best_architecture/pull/648) -* Update default value for role filter scopes by [@wu-clan](https://github.com/wu-clan) in [#649](https://github.com/fastapi-practices/fastapi_best_architecture/pull/649) -* Fix data permission condition for filter data by [@wu-clan](https://github.com/wu-clan) in [#650](https://github.com/fastapi-practices/fastapi_best_architecture/pull/650) +* Update changelog for v1.4.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#639](https://github.com/fastapi-practices/fastapi_best_architecture/pull/639) +* Fix the role update business variables by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#640](https://github.com/fastapi-practices/fastapi_best_architecture/pull/640) +* Fix the menu delete interface arg description by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#641](https://github.com/fastapi-practices/fastapi_best_architecture/pull/641) +* Fix the filter of query all menus by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#642](https://github.com/fastapi-practices/fastapi_best_architecture/pull/642) +* Refactor routes to better align with RESTful by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#645](https://github.com/fastapi-practices/fastapi_best_architecture/pull/645) +* Update the server startup time to string by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#646](https://github.com/fastapi-practices/fastapi_best_architecture/pull/646) +* Add get all data scope rules interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#647](https://github.com/fastapi-practices/fastapi_best_architecture/pull/647) +* Add data permission condition for filter data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#648](https://github.com/fastapi-practices/fastapi_best_architecture/pull/648) +* Update default value for role filter scopes by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#649](https://github.com/fastapi-practices/fastapi_best_architecture/pull/649) +* Fix data permission condition for filter data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#650](https://github.com/fastapi-practices/fastapi_best_architecture/pull/650) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.2...v1.4.3 @@ -583,20 +767,20 @@ -# [v1.4.2](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.4.2) - 2025-05-29 +# [v1.4.2](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.4.2) - 2025-05-29 ## What's Changed -* Update changelog for v1.4.1 by [@wu-clan](https://github.com/wu-clan) in [#630](https://github.com/fastapi-practices/fastapi_best_architecture/pull/630) -* Update non-linked sidebar support by [@wu-clan](https://github.com/wu-clan) in [#633](https://github.com/fastapi-practices/fastapi_best_architecture/pull/633) -* Update the captcha invalidation error class by [@wu-clan](https://github.com/wu-clan) in [#634](https://github.com/fastapi-practices/fastapi_best_architecture/pull/634) -* Optimize role-related data processing performance by [@wu-clan](https://github.com/wu-clan) in [#635](https://github.com/fastapi-practices/fastapi_best_architecture/pull/635) -* Optimize install and build of plugin zip by [@wu-clan](https://github.com/wu-clan) in [#636](https://github.com/fastapi-practices/fastapi_best_architecture/pull/636) -* Fix auto-increment id for postgres init data by [@huyuwei1996](https://github.com/huyuwei1996) in [#632](https://github.com/fastapi-practices/fastapi_best_architecture/pull/632) -* Fix: prevent overwriting existing init files in code generator by [@lin-wu-1990](https://github.com/lin-wu-1990) in [#637](https://github.com/fastapi-practices/fastapi_best_architecture/pull/637) -* Simplify the user info update business by [@wu-clan](https://github.com/wu-clan) in [#638](https://github.com/fastapi-practices/fastapi_best_architecture/pull/638) +* Update changelog for v1.4.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#630](https://github.com/fastapi-practices/fastapi_best_architecture/pull/630) +* Update non-linked sidebar support by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#633](https://github.com/fastapi-practices/fastapi_best_architecture/pull/633) +* Update the captcha invalidation error class by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#634](https://github.com/fastapi-practices/fastapi_best_architecture/pull/634) +* Optimize role-related data processing performance by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#635](https://github.com/fastapi-practices/fastapi_best_architecture/pull/635) +* Optimize install and build of plugin zip by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#636](https://github.com/fastapi-practices/fastapi_best_architecture/pull/636) +* Fix auto-increment id for postgres init data by [@huyuwei1996](https://github.com/huyuwei1996) in [fastapi-practices/fastapi_best_architecture#632](https://github.com/fastapi-practices/fastapi_best_architecture/pull/632) +* Fix: prevent overwriting existing init files in code generator by [@lin-wu-1990](https://github.com/lin-wu-1990) in [fastapi-practices/fastapi_best_architecture#637](https://github.com/fastapi-practices/fastapi_best_architecture/pull/637) +* Simplify the user info update business by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#638](https://github.com/fastapi-practices/fastapi_best_architecture/pull/638) ## New Contributors -* [@lin-wu-1990](https://github.com/lin-wu-1990) made their first contribution in [#637](https://github.com/fastapi-practices/fastapi_best_architecture/pull/637) +* [@lin-wu-1990](https://github.com/lin-wu-1990) made their first contribution in [fastapi-practices/fastapi_best_architecture#637](https://github.com/fastapi-practices/fastapi_best_architecture/pull/637) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.1...v1.4.2 @@ -609,15 +793,15 @@ -# [v1.4.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.4.1) - 2025-05-25 +# [v1.4.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.4.1) - 2025-05-25 ## What's Changed -* Update changelog for v1.4.0 by [@wu-clan](https://github.com/wu-clan) in [#621](https://github.com/fastapi-practices/fastapi_best_architecture/pull/621) -* Update the menu path and type columns by [@wu-clan](https://github.com/wu-clan) in [#622](https://github.com/fastapi-practices/fastapi_best_architecture/pull/622) -* Add the deepwiki badge to README by [@wu-clan](https://github.com/wu-clan) in [#623](https://github.com/fastapi-practices/fastapi_best_architecture/pull/623) -* Refactor the system token to online users by [@wu-clan](https://github.com/wu-clan) in [#624](https://github.com/fastapi-practices/fastapi_best_architecture/pull/624) -* Update the token check for logout interface by [@wu-clan](https://github.com/wu-clan) in [#625](https://github.com/fastapi-practices/fastapi_best_architecture/pull/625) -* Update the token decode for logout interface by [@wu-clan](https://github.com/wu-clan) in [#629](https://github.com/fastapi-practices/fastapi_best_architecture/pull/629) +* Update changelog for v1.4.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#621](https://github.com/fastapi-practices/fastapi_best_architecture/pull/621) +* Update the menu path and type columns by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#622](https://github.com/fastapi-practices/fastapi_best_architecture/pull/622) +* Add the deepwiki badge to README by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#623](https://github.com/fastapi-practices/fastapi_best_architecture/pull/623) +* Refactor the system token to online users by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#624](https://github.com/fastapi-practices/fastapi_best_architecture/pull/624) +* Update the token check for logout interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#625](https://github.com/fastapi-practices/fastapi_best_architecture/pull/625) +* Update the token decode for logout interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#629](https://github.com/fastapi-practices/fastapi_best_architecture/pull/629) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.0...v1.4.1 @@ -630,19 +814,19 @@ -# [v1.4.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.4.0) - 2025-05-22 +# [v1.4.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.4.0) - 2025-05-22 ## What's Changed -* Update changelog for v1.3.0 by [@wu-clan](https://github.com/wu-clan) in [#605](https://github.com/fastapi-practices/fastapi_best_architecture/pull/605) -* Add new plugin status check interface by [@wu-clan](https://github.com/wu-clan) in [#606](https://github.com/fastapi-practices/fastapi_best_architecture/pull/606) -* Update the new plugin status to changed by [@wu-clan](https://github.com/wu-clan) in [#607](https://github.com/fastapi-practices/fastapi_best_architecture/pull/607) -* Fix the task result schema param type by [@wu-clan](https://github.com/wu-clan) in [#611](https://github.com/fastapi-practices/fastapi_best_architecture/pull/611) -* Fix the plugin status update logic by [@wu-clan](https://github.com/wu-clan) in [#613](https://github.com/fastapi-practices/fastapi_best_architecture/pull/613) -* Update uninstall and build plugin api method by [@wu-clan](https://github.com/wu-clan) in [#614](https://github.com/fastapi-practices/fastapi_best_architecture/pull/614) -* Fix non-asyncio nested async IO by [@wu-clan](https://github.com/wu-clan) in [#610](https://github.com/fastapi-practices/fastapi_best_architecture/pull/610) -* Update the build plugin api params by [@wu-clan](https://github.com/wu-clan) in [#615](https://github.com/fastapi-practices/fastapi_best_architecture/pull/615) -* Update uv installation in docker deploy by [@wu-clan](https://github.com/wu-clan) in [#619](https://github.com/fastapi-practices/fastapi_best_architecture/pull/619) -* Update the OAuth2 module to plugin by [@wu-clan](https://github.com/wu-clan) in [#620](https://github.com/fastapi-practices/fastapi_best_architecture/pull/620) +* Update changelog for v1.3.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#605](https://github.com/fastapi-practices/fastapi_best_architecture/pull/605) +* Add new plugin status check interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#606](https://github.com/fastapi-practices/fastapi_best_architecture/pull/606) +* Update the new plugin status to changed by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#607](https://github.com/fastapi-practices/fastapi_best_architecture/pull/607) +* Fix the task result schema param type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#611](https://github.com/fastapi-practices/fastapi_best_architecture/pull/611) +* Fix the plugin status update logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#613](https://github.com/fastapi-practices/fastapi_best_architecture/pull/613) +* Update uninstall and build plugin api method by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#614](https://github.com/fastapi-practices/fastapi_best_architecture/pull/614) +* Fix non-asyncio nested async IO by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#610](https://github.com/fastapi-practices/fastapi_best_architecture/pull/610) +* Update the build plugin api params by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#615](https://github.com/fastapi-practices/fastapi_best_architecture/pull/615) +* Update uv installation in docker deploy by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#619](https://github.com/fastapi-practices/fastapi_best_architecture/pull/619) +* Update the OAuth2 module to plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#620](https://github.com/fastapi-practices/fastapi_best_architecture/pull/620) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.3.0...v1.4.0 @@ -655,15 +839,15 @@ -# [v1.3.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.3.0) - 2025-05-16 +# [v1.3.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.3.0) - 2025-05-16 ## What's Changed -* Update changelog for v1.2.0 by [@wu-clan](https://github.com/wu-clan) in [#598](https://github.com/fastapi-practices/fastapi_best_architecture/pull/598) -* Simplify apps and plugins config method by [@wu-clan](https://github.com/wu-clan) in [#600](https://github.com/fastapi-practices/fastapi_best_architecture/pull/600) -* Add plugin info config and interfaces by [@wu-clan](https://github.com/wu-clan) in [#601](https://github.com/fastapi-practices/fastapi_best_architecture/pull/601) -* Fix the fastapi cli startup event loop by [@wu-clan](https://github.com/wu-clan) in [#602](https://github.com/fastapi-practices/fastapi_best_architecture/pull/602) -* Optimize the zip plugin install logic by [@wu-clan](https://github.com/wu-clan) in [#603](https://github.com/fastapi-practices/fastapi_best_architecture/pull/603) -* Update the casbin RBAC module path by [@wu-clan](https://github.com/wu-clan) in [#604](https://github.com/fastapi-practices/fastapi_best_architecture/pull/604) +* Update changelog for v1.2.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#598](https://github.com/fastapi-practices/fastapi_best_architecture/pull/598) +* Simplify apps and plugins config method by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#600](https://github.com/fastapi-practices/fastapi_best_architecture/pull/600) +* Add plugin info config and interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#601](https://github.com/fastapi-practices/fastapi_best_architecture/pull/601) +* Fix the fastapi cli startup event loop by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#602](https://github.com/fastapi-practices/fastapi_best_architecture/pull/602) +* Optimize the zip plugin install logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#603](https://github.com/fastapi-practices/fastapi_best_architecture/pull/603) +* Update the casbin RBAC module path by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#604](https://github.com/fastapi-practices/fastapi_best_architecture/pull/604) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.2.0...v1.3.0 @@ -676,15 +860,15 @@ -# [v1.2.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.2.0) - 2025-05-01 +# [v1.2.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.2.0) - 2025-05-01 ## What's Changed -* Update changelog for v1.1.2 by [@wu-clan](https://github.com/wu-clan) in [#589](https://github.com/fastapi-practices/fastapi_best_architecture/pull/589) -* Update code generator table columns by [@wu-clan](https://github.com/wu-clan) in [#590](https://github.com/fastapi-practices/fastapi_best_architecture/pull/590) -* Update the default RBAC solution by [@wu-clan](https://github.com/wu-clan) in [#593](https://github.com/fastapi-practices/fastapi_best_architecture/pull/593) -* Optimize the server information retrieval by [@wu-clan](https://github.com/wu-clan) in [#595](https://github.com/fastapi-practices/fastapi_best_architecture/pull/595) -* Refactor the data rule to scope rule by [@wu-clan](https://github.com/wu-clan) in [#596](https://github.com/fastapi-practices/fastapi_best_architecture/pull/596) -* Update the SQL script for creat tables by [@wu-clan](https://github.com/wu-clan) in [#597](https://github.com/fastapi-practices/fastapi_best_architecture/pull/597) +* Update changelog for v1.1.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#589](https://github.com/fastapi-practices/fastapi_best_architecture/pull/589) +* Update code generator table columns by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#590](https://github.com/fastapi-practices/fastapi_best_architecture/pull/590) +* Update the default RBAC solution by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#593](https://github.com/fastapi-practices/fastapi_best_architecture/pull/593) +* Optimize the server information retrieval by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#595](https://github.com/fastapi-practices/fastapi_best_architecture/pull/595) +* Refactor the data rule to scope rule by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#596](https://github.com/fastapi-practices/fastapi_best_architecture/pull/596) +* Update the SQL script for creat tables by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#597](https://github.com/fastapi-practices/fastapi_best_architecture/pull/597) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.2...v1.2.0 @@ -697,15 +881,15 @@ -# [v1.1.2](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.1.2) - 2025-04-23 +# [v1.1.2](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.1.2) - 2025-04-23 ## What's Changed -* Update the changelog for v1.1.1 by [@wu-clan](https://github.com/wu-clan) in [#583](https://github.com/fastapi-practices/fastapi_best_architecture/pull/583) -* Fix the condition to query menu by title by [@wu-clan](https://github.com/wu-clan) in [#584](https://github.com/fastapi-practices/fastapi_best_architecture/pull/584) -* Fix cache cleanup when updating role menu by [@wu-clan](https://github.com/wu-clan) in [#585](https://github.com/fastapi-practices/fastapi_best_architecture/pull/585) -* Optimize the userinfo cache cleaning logic by [@wu-clan](https://github.com/wu-clan) in [#586](https://github.com/fastapi-practices/fastapi_best_architecture/pull/586) -* Bump fastapi pagination from 0.12.34 to 0.13.0 by [@wu-clan](https://github.com/wu-clan) in [#587](https://github.com/fastapi-practices/fastapi_best_architecture/pull/587) -* Update the routing style of the task app by [@wu-clan](https://github.com/wu-clan) in [#588](https://github.com/fastapi-practices/fastapi_best_architecture/pull/588) +* Update the changelog for v1.1.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#583](https://github.com/fastapi-practices/fastapi_best_architecture/pull/583) +* Fix the condition to query menu by title by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#584](https://github.com/fastapi-practices/fastapi_best_architecture/pull/584) +* Fix cache cleanup when updating role menu by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#585](https://github.com/fastapi-practices/fastapi_best_architecture/pull/585) +* Optimize the userinfo cache cleaning logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#586](https://github.com/fastapi-practices/fastapi_best_architecture/pull/586) +* Bump fastapi pagination from 0.12.34 to 0.13.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#587](https://github.com/fastapi-practices/fastapi_best_architecture/pull/587) +* Update the routing style of the task app by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#588](https://github.com/fastapi-practices/fastapi_best_architecture/pull/588) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.1...v1.1.2 @@ -718,12 +902,12 @@ -# [v1.1.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.1.1) - 2025-04-18 +# [v1.1.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.1.1) - 2025-04-18 ## What's Changed -* Update changelog for v1.1.0 by [@wu-clan](https://github.com/wu-clan) in [#580](https://github.com/fastapi-practices/fastapi_best_architecture/pull/580) -* Fix the plugin system route injection by [@wu-clan](https://github.com/wu-clan) in [#581](https://github.com/fastapi-practices/fastapi_best_architecture/pull/581) -* Fix list query in the dict plugin by [@wu-clan](https://github.com/wu-clan) in [#582](https://github.com/fastapi-practices/fastapi_best_architecture/pull/582) +* Update changelog for v1.1.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#580](https://github.com/fastapi-practices/fastapi_best_architecture/pull/580) +* Fix the plugin system route injection by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#581](https://github.com/fastapi-practices/fastapi_best_architecture/pull/581) +* Fix list query in the dict plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#582](https://github.com/fastapi-practices/fastapi_best_architecture/pull/582) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.0...v1.1.1 @@ -736,17 +920,17 @@ -# [v1.1.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.1.0) - 2025-04-17 +# [v1.1.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.1.0) - 2025-04-17 ## What's Changed -* Update the changelog for v1.0.5 by [@wu-clan](https://github.com/wu-clan) in [#572](https://github.com/fastapi-practices/fastapi_best_architecture/pull/572) -* Update the default value for some functions by [@wu-clan](https://github.com/wu-clan) in [#573](https://github.com/fastapi-practices/fastapi_best_architecture/pull/573) -* Optimize the file structure of code generator by [@wu-clan](https://github.com/wu-clan) in [#574](https://github.com/fastapi-practices/fastapi_best_architecture/pull/574) -* Update casbin RBAC verify to dynamic import by [@wu-clan](https://github.com/wu-clan) in [#576](https://github.com/fastapi-practices/fastapi_best_architecture/pull/576) -* Update unique columns in dict models by [@wu-clan](https://github.com/wu-clan) in [#577](https://github.com/fastapi-practices/fastapi_best_architecture/pull/577) -* Update the code generator to plugin by [@wu-clan](https://github.com/wu-clan) in [#578](https://github.com/fastapi-practices/fastapi_best_architecture/pull/578) -* Fix avatar url type of update avatar by [@huyuwei1996](https://github.com/huyuwei1996) in [#575](https://github.com/fastapi-practices/fastapi_best_architecture/pull/575) -* Update code generator file and table naming by [@wu-clan](https://github.com/wu-clan) in [#579](https://github.com/fastapi-practices/fastapi_best_architecture/pull/579) +* Update the changelog for v1.0.5 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#572](https://github.com/fastapi-practices/fastapi_best_architecture/pull/572) +* Update the default value for some functions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#573](https://github.com/fastapi-practices/fastapi_best_architecture/pull/573) +* Optimize the file structure of code generator by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#574](https://github.com/fastapi-practices/fastapi_best_architecture/pull/574) +* Update casbin RBAC verify to dynamic import by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#576](https://github.com/fastapi-practices/fastapi_best_architecture/pull/576) +* Update unique columns in dict models by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#577](https://github.com/fastapi-practices/fastapi_best_architecture/pull/577) +* Update the code generator to plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#578](https://github.com/fastapi-practices/fastapi_best_architecture/pull/578) +* Fix avatar url type of update avatar by [@huyuwei1996](https://github.com/huyuwei1996) in [fastapi-practices/fastapi_best_architecture#575](https://github.com/fastapi-practices/fastapi_best_architecture/pull/575) +* Update code generator file and table naming by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#579](https://github.com/fastapi-practices/fastapi_best_architecture/pull/579) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.5...v1.1.0 @@ -760,22 +944,22 @@ -# [v1.0.5](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.0.5) - 2025-04-09 +# [v1.0.5](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.0.5) - 2025-04-09 ## What's Changed -* Update the changelog for v1.0.4 by [@wu-clan](https://github.com/wu-clan) in [#558](https://github.com/fastapi-practices/fastapi_best_architecture/pull/558) -* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#559](https://github.com/fastapi-practices/fastapi_best_architecture/pull/559) -* Add python 3.13 to GitHub ci by [@wu-clan](https://github.com/wu-clan) in [#560](https://github.com/fastapi-practices/fastapi_best_architecture/pull/560) -* Update the system config to plugin by [@wu-clan](https://github.com/wu-clan) in [#561](https://github.com/fastapi-practices/fastapi_best_architecture/pull/561) -* Update dict data and type to plugin by [@wu-clan](https://github.com/wu-clan) in [#562](https://github.com/fastapi-practices/fastapi_best_architecture/pull/562) -* Update menu and add vben5 compatibility by [@wu-clan](https://github.com/wu-clan) in [#563](https://github.com/fastapi-practices/fastapi_best_architecture/pull/563) -* Update the vben5 tree data structure by [@wu-clan](https://github.com/wu-clan) in [#564](https://github.com/fastapi-practices/fastapi_best_architecture/pull/564) -* Update custom validation error messages by [@wu-clan](https://github.com/wu-clan) in [#566](https://github.com/fastapi-practices/fastapi_best_architecture/pull/566) -* Update the number of pagination le by [@wu-clan](https://github.com/wu-clan) in [#565](https://github.com/fastapi-practices/fastapi_best_architecture/pull/565) -* Fix the login password verification by [@wu-clan](https://github.com/wu-clan) in [#568](https://github.com/fastapi-practices/fastapi_best_architecture/pull/568) -* Fix the failure hook of celery task by [@wu-clan](https://github.com/wu-clan) in [#569](https://github.com/fastapi-practices/fastapi_best_architecture/pull/569) -* Bump fastapi oauth2 from 0.0.1a2 to 0.0.1 by [@wu-clan](https://github.com/wu-clan) in [#570](https://github.com/fastapi-practices/fastapi_best_architecture/pull/570) -* Fix the log rule in gitignore by [@wu-clan](https://github.com/wu-clan) in [#571](https://github.com/fastapi-practices/fastapi_best_architecture/pull/571) +* Update the changelog for v1.0.4 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#558](https://github.com/fastapi-practices/fastapi_best_architecture/pull/558) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#559](https://github.com/fastapi-practices/fastapi_best_architecture/pull/559) +* Add python 3.13 to GitHub ci by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#560](https://github.com/fastapi-practices/fastapi_best_architecture/pull/560) +* Update the system config to plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#561](https://github.com/fastapi-practices/fastapi_best_architecture/pull/561) +* Update dict data and type to plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#562](https://github.com/fastapi-practices/fastapi_best_architecture/pull/562) +* Update menu and add vben5 compatibility by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#563](https://github.com/fastapi-practices/fastapi_best_architecture/pull/563) +* Update the vben5 tree data structure by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#564](https://github.com/fastapi-practices/fastapi_best_architecture/pull/564) +* Update custom validation error messages by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#566](https://github.com/fastapi-practices/fastapi_best_architecture/pull/566) +* Update the number of pagination le by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#565](https://github.com/fastapi-practices/fastapi_best_architecture/pull/565) +* Fix the login password verification by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#568](https://github.com/fastapi-practices/fastapi_best_architecture/pull/568) +* Fix the failure hook of celery task by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#569](https://github.com/fastapi-practices/fastapi_best_architecture/pull/569) +* Bump fastapi oauth2 from 0.0.1a2 to 0.0.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#570](https://github.com/fastapi-practices/fastapi_best_architecture/pull/570) +* Fix the log rule in gitignore by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#571](https://github.com/fastapi-practices/fastapi_best_architecture/pull/571) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.4...v1.0.5 @@ -788,27 +972,27 @@ -# [v1.0.4](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.0.4) - 2025-03-28 +# [v1.0.4](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.0.4) - 2025-03-28 ## What's Changed -* Update the changelog for v1.0.3 by [@wu-clan](https://github.com/wu-clan) in [#543](https://github.com/fastapi-practices/fastapi_best_architecture/pull/543) -* Updated the backend contribution guide by [@wu-clan](https://github.com/wu-clan) in [#544](https://github.com/fastapi-practices/fastapi_best_architecture/pull/544) -* Optimize the return of relationship interfaces by [@wu-clan](https://github.com/wu-clan) in [#545](https://github.com/fastapi-practices/fastapi_best_architecture/pull/545) -* Optimize the dynamic import of data models by [@wu-clan](https://github.com/wu-clan) in [#546](https://github.com/fastapi-practices/fastapi_best_architecture/pull/546) -* Update git and docker ignore files by [@wu-clan](https://github.com/wu-clan) in [#547](https://github.com/fastapi-practices/fastapi_best_architecture/pull/547) -* Optimize dependencies to reduce package size by [@wu-clan](https://github.com/wu-clan) in [#548](https://github.com/fastapi-practices/fastapi_best_architecture/pull/548) -* Fix async install plugin dependencies for windows by [@wu-clan](https://github.com/wu-clan) in [#549](https://github.com/fastapi-practices/fastapi_best_architecture/pull/549) -* Fix return schema of the config api by [@wu-clan](https://github.com/wu-clan) in [#551](https://github.com/fastapi-practices/fastapi_best_architecture/pull/551) -* Optimize schemas with model relationships by [@wu-clan](https://github.com/wu-clan) in [#552](https://github.com/fastapi-practices/fastapi_best_architecture/pull/552) -* Fix filters for opera log query list by [@ThankCat](https://github.com/ThankCat) in [#554](https://github.com/fastapi-practices/fastapi_best_architecture/pull/554) -* Fix the celery env in docker compose by [@wu-clan](https://github.com/wu-clan) in [#555](https://github.com/fastapi-practices/fastapi_best_architecture/pull/555) -* Update volumes of redis in docker compose by [@wu-clan](https://github.com/wu-clan) in [#556](https://github.com/fastapi-practices/fastapi_best_architecture/pull/556) -* Fix the query for the sub department by [@PoetryL](https://github.com/PoetryL) in [#557](https://github.com/fastapi-practices/fastapi_best_architecture/pull/557) -* Optimize codes and comments with cursor by [@wu-clan](https://github.com/wu-clan) in [#550](https://github.com/fastapi-practices/fastapi_best_architecture/pull/550) +* Update the changelog for v1.0.3 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#543](https://github.com/fastapi-practices/fastapi_best_architecture/pull/543) +* Updated the backend contribution guide by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#544](https://github.com/fastapi-practices/fastapi_best_architecture/pull/544) +* Optimize the return of relationship interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#545](https://github.com/fastapi-practices/fastapi_best_architecture/pull/545) +* Optimize the dynamic import of data models by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#546](https://github.com/fastapi-practices/fastapi_best_architecture/pull/546) +* Update git and docker ignore files by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#547](https://github.com/fastapi-practices/fastapi_best_architecture/pull/547) +* Optimize dependencies to reduce package size by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#548](https://github.com/fastapi-practices/fastapi_best_architecture/pull/548) +* Fix async install plugin dependencies for windows by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#549](https://github.com/fastapi-practices/fastapi_best_architecture/pull/549) +* Fix return schema of the config api by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#551](https://github.com/fastapi-practices/fastapi_best_architecture/pull/551) +* Optimize schemas with model relationships by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#552](https://github.com/fastapi-practices/fastapi_best_architecture/pull/552) +* Fix filters for opera log query list by [@ThankCat](https://github.com/ThankCat) in [fastapi-practices/fastapi_best_architecture#554](https://github.com/fastapi-practices/fastapi_best_architecture/pull/554) +* Fix the celery env in docker compose by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#555](https://github.com/fastapi-practices/fastapi_best_architecture/pull/555) +* Update volumes of redis in docker compose by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#556](https://github.com/fastapi-practices/fastapi_best_architecture/pull/556) +* Fix the query for the sub department by [@PoetryL](https://github.com/PoetryL) in [fastapi-practices/fastapi_best_architecture#557](https://github.com/fastapi-practices/fastapi_best_architecture/pull/557) +* Optimize codes and comments with cursor by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#550](https://github.com/fastapi-practices/fastapi_best_architecture/pull/550) ## New Contributors -* [@ThankCat](https://github.com/ThankCat) made their first contribution in [#554](https://github.com/fastapi-practices/fastapi_best_architecture/pull/554) -* [@PoetryL](https://github.com/PoetryL) made their first contribution in [#557](https://github.com/fastapi-practices/fastapi_best_architecture/pull/557) +* [@ThankCat](https://github.com/ThankCat) made their first contribution in [fastapi-practices/fastapi_best_architecture#554](https://github.com/fastapi-practices/fastapi_best_architecture/pull/554) +* [@PoetryL](https://github.com/PoetryL) made their first contribution in [fastapi-practices/fastapi_best_architecture#557](https://github.com/fastapi-practices/fastapi_best_architecture/pull/557) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.3...v1.0.4 @@ -822,16 +1006,16 @@ -# [v1.0.3](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.0.3) - 2025-03-11 +# [v1.0.3](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.0.3) - 2025-03-11 ## What's Changed -* Update the changelog for v1.0.2 by [@wu-clan](https://github.com/wu-clan) in [#536](https://github.com/fastapi-practices/fastapi_best_architecture/pull/536) -* Update docker scripts in backend README by [@wu-clan](https://github.com/wu-clan) in [#537](https://github.com/fastapi-practices/fastapi_best_architecture/pull/537) -* Refactor toml and dependencies file dir by [@wu-clan](https://github.com/wu-clan) in [#538](https://github.com/fastapi-practices/fastapi_best_architecture/pull/538) -* Fix typos in Dockerfile comments by [@huyuwei1996](https://github.com/huyuwei1996) in [#539](https://github.com/fastapi-practices/fastapi_best_architecture/pull/539) -* Fix Dockerfile mounts for dependency installation by [@huyuwei1996](https://github.com/huyuwei1996) in [#540](https://github.com/fastapi-practices/fastapi_best_architecture/pull/540) -* Add Aliyun mirror to PyPI index in pyproject.toml by [@huyuwei1996](https://github.com/huyuwei1996) in [#541](https://github.com/fastapi-practices/fastapi_best_architecture/pull/541) -* Update docker scripts and nginx conf by [@wu-clan](https://github.com/wu-clan) in [#542](https://github.com/fastapi-practices/fastapi_best_architecture/pull/542) +* Update the changelog for v1.0.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#536](https://github.com/fastapi-practices/fastapi_best_architecture/pull/536) +* Update docker scripts in backend README by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#537](https://github.com/fastapi-practices/fastapi_best_architecture/pull/537) +* Refactor toml and dependencies file dir by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#538](https://github.com/fastapi-practices/fastapi_best_architecture/pull/538) +* Fix typos in Dockerfile comments by [@huyuwei1996](https://github.com/huyuwei1996) in [fastapi-practices/fastapi_best_architecture#539](https://github.com/fastapi-practices/fastapi_best_architecture/pull/539) +* Fix Dockerfile mounts for dependency installation by [@huyuwei1996](https://github.com/huyuwei1996) in [fastapi-practices/fastapi_best_architecture#540](https://github.com/fastapi-practices/fastapi_best_architecture/pull/540) +* Add Aliyun mirror to PyPI index in pyproject.toml by [@huyuwei1996](https://github.com/huyuwei1996) in [fastapi-practices/fastapi_best_architecture#541](https://github.com/fastapi-practices/fastapi_best_architecture/pull/541) +* Update docker scripts and nginx conf by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#542](https://github.com/fastapi-practices/fastapi_best_architecture/pull/542) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.2...v1.0.3 @@ -845,13 +1029,13 @@ -# [v1.0.2](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.0.2) - 2025-03-01 +# [v1.0.2](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.0.2) - 2025-03-01 ## What's Changed -* Update the changelog for v1.0.1 by [@wu-clan](https://github.com/wu-clan) in [#532](https://github.com/fastapi-practices/fastapi_best_architecture/pull/532) -* Fix celery async task worker pool by [@wu-clan](https://github.com/wu-clan) in [#533](https://github.com/fastapi-practices/fastapi_best_architecture/pull/533) -* Add log module root and output levels by [@wu-clan](https://github.com/wu-clan) in [#534](https://github.com/fastapi-practices/fastapi_best_architecture/pull/534) -* Add plugin related interfaces by [@wu-clan](https://github.com/wu-clan) in [#535](https://github.com/fastapi-practices/fastapi_best_architecture/pull/535) +* Update the changelog for v1.0.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#532](https://github.com/fastapi-practices/fastapi_best_architecture/pull/532) +* Fix celery async task worker pool by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#533](https://github.com/fastapi-practices/fastapi_best_architecture/pull/533) +* Add log module root and output levels by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#534](https://github.com/fastapi-practices/fastapi_best_architecture/pull/534) +* Add plugin related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#535](https://github.com/fastapi-practices/fastapi_best_architecture/pull/535) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.1...v1.0.2 @@ -864,18 +1048,18 @@ -# [v1.0.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.0.1) - 2025-02-26 +# [v1.0.1](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.0.1) - 2025-02-26 ## What's Changed -* Update the changelog for v1.0.0 by [@wu-clan](https://github.com/wu-clan) in [#524](https://github.com/fastapi-practices/fastapi_best_architecture/pull/524) -* Add missing volume config for docker deploy by [@huyuwei1996](https://github.com/huyuwei1996) in [#525](https://github.com/fastapi-practices/fastapi_best_architecture/pull/525) -* Add async attrs for sqla mapped base by [@wu-clan](https://github.com/wu-clan) in [#528](https://github.com/fastapi-practices/fastapi_best_architecture/pull/528) -* Add sqlalchemy connection pool config by [@wu-clan](https://github.com/wu-clan) in [#529](https://github.com/fastapi-practices/fastapi_best_architecture/pull/529) -* Fix the sql script for init data by [@wu-clan](https://github.com/wu-clan) in [#530](https://github.com/fastapi-practices/fastapi_best_architecture/pull/530) -* Optimize Dockerfile for faster builds by [@huyuwei1996](https://github.com/huyuwei1996) in [#526](https://github.com/fastapi-practices/fastapi_best_architecture/pull/526) +* Update the changelog for v1.0.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#524](https://github.com/fastapi-practices/fastapi_best_architecture/pull/524) +* Add missing volume config for docker deploy by [@huyuwei1996](https://github.com/huyuwei1996) in [fastapi-practices/fastapi_best_architecture#525](https://github.com/fastapi-practices/fastapi_best_architecture/pull/525) +* Add async attrs for sqla mapped base by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#528](https://github.com/fastapi-practices/fastapi_best_architecture/pull/528) +* Add sqlalchemy connection pool config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#529](https://github.com/fastapi-practices/fastapi_best_architecture/pull/529) +* Fix the sql script for init data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#530](https://github.com/fastapi-practices/fastapi_best_architecture/pull/530) +* Optimize Dockerfile for faster builds by [@huyuwei1996](https://github.com/huyuwei1996) in [fastapi-practices/fastapi_best_architecture#526](https://github.com/fastapi-practices/fastapi_best_architecture/pull/526) ## New Contributors -* [@huyuwei1996](https://github.com/huyuwei1996) made their first contribution in [#525](https://github.com/fastapi-practices/fastapi_best_architecture/pull/525) +* [@huyuwei1996](https://github.com/huyuwei1996) made their first contribution in [fastapi-practices/fastapi_best_architecture#525](https://github.com/fastapi-practices/fastapi_best_architecture/pull/525) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.0...v1.0.1 @@ -888,324 +1072,324 @@ -# [v1.0.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.0.0) - 2025-02-24 +# [v1.0.0](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.0.0) - 2025-02-24 ## What's Changed -* add base code by [@wu-clan](https://github.com/wu-clan) in [#12](https://github.com/fastapi-practices/fastapi_best_architecture/pull/12) -* fix get_user_info func return None [#9](https://github.com/fastapi-practices/fastapi_best_architecture/issues/9) by [@wu-clan](https://github.com/wu-clan) in [#14](https://github.com/fastapi-practices/fastapi_best_architecture/pull/14) -* simplify user apis [#11](https://github.com/fastapi-practices/fastapi_best_architecture/issues/11) by [@wu-clan](https://github.com/wu-clan) in [#18](https://github.com/fastapi-practices/fastapi_best_architecture/pull/18) -* Add environment variable management and fix Pydantic validation error by [@downdawn](https://github.com/downdawn) in [#15](https://github.com/fastapi-practices/fastapi_best_architecture/pull/15) -* update the ruff rules and format the code by [@wu-clan](https://github.com/wu-clan) in [#24](https://github.com/fastapi-practices/fastapi_best_architecture/pull/24) -* improving project configuration by [@wu-clan](https://github.com/wu-clan) in [#25](https://github.com/fastapi-practices/fastapi_best_architecture/pull/25) -* update to python3.10 by [@downdawn](https://github.com/downdawn) in [#29](https://github.com/fastapi-practices/fastapi_best_architecture/pull/29) -* update Dockerfile and docker-compose.yml by [@wu-clan](https://github.com/wu-clan) in [#31](https://github.com/fastapi-practices/fastapi_best_architecture/pull/31) -* Update the sub routers setting in the subdirectory by [@wu-clan](https://github.com/wu-clan) in [#32](https://github.com/fastapi-practices/fastapi_best_architecture/pull/32) -* add the get project config api by [@wu-clan](https://github.com/wu-clan) in [#33](https://github.com/fastapi-practices/fastapi_best_architecture/pull/33) -* add test code by [@wu-clan](https://github.com/wu-clan) in [#37](https://github.com/fastapi-practices/fastapi_best_architecture/pull/37) -* fix that the data validation global exception handler does not work by [@wu-clan](https://github.com/wu-clan) in [#40](https://github.com/fastapi-practices/fastapi_best_architecture/pull/40) -* Fix the swagger form login structure abnormality by [@wu-clan](https://github.com/wu-clan) in [#46](https://github.com/fastapi-practices/fastapi_best_architecture/pull/46) -* Bump starlette from 0.26.1 to 0.27.0 by [@dependabot](https://github.com/dependabot) in [#48](https://github.com/fastapi-practices/fastapi_best_architecture/pull/48) -* add rbac authorization by [@wu-clan](https://github.com/wu-clan) in [#41](https://github.com/fastapi-practices/fastapi_best_architecture/pull/41) -* Bump fastapi from 0.95.0 to 0.95.2 by [@wu-clan](https://github.com/wu-clan) in [#53](https://github.com/fastapi-practices/fastapi_best_architecture/pull/53) -* Update the uniform return method to success by [@wu-clan](https://github.com/wu-clan) in [#55](https://github.com/fastapi-practices/fastapi_best_architecture/pull/55) -* add token storage and logout by [@downdawn](https://github.com/downdawn) in [#57](https://github.com/fastapi-practices/fastapi_best_architecture/pull/57) -* fix jwt parameter parsing error by [@downdawn](https://github.com/downdawn) in [#61](https://github.com/fastapi-practices/fastapi_best_architecture/pull/61) -* add token refreshing mechanism by [@wu-clan](https://github.com/wu-clan) in [#62](https://github.com/fastapi-practices/fastapi_best_architecture/pull/62) -* Update uniform return to custom encoder by [@wu-clan](https://github.com/wu-clan) in [#60](https://github.com/fastapi-practices/fastapi_best_architecture/pull/60) -* update token default exception return message by [@wu-clan](https://github.com/wu-clan) in [#65](https://github.com/fastapi-practices/fastapi_best_architecture/pull/65) -* add English and Chinese README jump links by [@wu-clan](https://github.com/wu-clan) in [#66](https://github.com/fastapi-practices/fastapi_best_architecture/pull/66) -* update token refresh expire time rule by [@wu-clan](https://github.com/wu-clan) in [#67](https://github.com/fastapi-practices/fastapi_best_architecture/pull/67) -* update the casbin to asynchronous by [@wu-clan](https://github.com/wu-clan) in [#69](https://github.com/fastapi-practices/fastapi_best_architecture/pull/69) -* Update tests structure. by [@downdawn](https://github.com/downdawn) in [#68](https://github.com/fastapi-practices/fastapi_best_architecture/pull/68) -* Add apis rate limiter by [@wu-clan](https://github.com/wu-clan) in [#72](https://github.com/fastapi-practices/fastapi_best_architecture/pull/72) -* add tests exclusion E402 rule by [@wu-clan](https://github.com/wu-clan) in [#73](https://github.com/fastapi-practices/fastapi_best_architecture/pull/73) -* update where query by [@downdawn](https://github.com/downdawn) in [#74](https://github.com/fastapi-practices/fastapi_best_architecture/pull/74) -* simplify crud method naming by [@wu-clan](https://github.com/wu-clan) in [#75](https://github.com/fastapi-practices/fastapi_best_architecture/pull/75) -* add login logs by [@wu-clan](https://github.com/wu-clan) in [#76](https://github.com/fastapi-practices/fastapi_best_architecture/pull/76) -* add different log files by [@wu-clan](https://github.com/wu-clan) in [#77](https://github.com/fastapi-practices/fastapi_best_architecture/pull/77) -* add offline ip location resolution by [@wu-clan](https://github.com/wu-clan) in [#78](https://github.com/fastapi-practices/fastapi_best_architecture/pull/78) -* add api module Interfaces by [@wu-clan](https://github.com/wu-clan) in [#79](https://github.com/fastapi-practices/fastapi_best_architecture/pull/79) -* update token handling logic by [@wu-clan](https://github.com/wu-clan) in [#83](https://github.com/fastapi-practices/fastapi_best_architecture/pull/83) -* add jwt authentication middleware by [@wu-clan](https://github.com/wu-clan) in [#84](https://github.com/fastapi-practices/fastapi_best_architecture/pull/84) -* Fix background task not executed by [@wu-clan](https://github.com/wu-clan) in [#86](https://github.com/fastapi-practices/fastapi_best_architecture/pull/86) -* Fix the merge issues by [@wu-clan](https://github.com/wu-clan) in [#87](https://github.com/fastapi-practices/fastapi_best_architecture/pull/87) -* Update docker one-click deployment by [@wu-clan](https://github.com/wu-clan) in [#88](https://github.com/fastapi-practices/fastapi_best_architecture/pull/88) -* Add role-related interfaces by [@wu-clan](https://github.com/wu-clan) in [#89](https://github.com/fastapi-practices/fastapi_best_architecture/pull/89) -* Bump cryptography from 39.0.1 to 41.0.0 by [@dependabot](https://github.com/dependabot) in [#90](https://github.com/fastapi-practices/fastapi_best_architecture/pull/90) -* Add assertion error handler. by [@wu-clan](https://github.com/wu-clan) in [#93](https://github.com/fastapi-practices/fastapi_best_architecture/pull/93) -* Add operation log related interfaces by [@wu-clan](https://github.com/wu-clan) in [#92](https://github.com/fastapi-practices/fastapi_best_architecture/pull/92) -* Fix user authorization lock by [@wu-clan](https://github.com/wu-clan) in [#94](https://github.com/fastapi-practices/fastapi_best_architecture/pull/94) -* Fix the opera log cost_time parameter by [@wu-clan](https://github.com/wu-clan) in [#95](https://github.com/fastapi-practices/fastapi_best_architecture/pull/95) -* Add os and browser parameters to opera log by [@wu-clan](https://github.com/wu-clan) in [#97](https://github.com/fastapi-practices/fastapi_best_architecture/pull/97) -* Uniform schema class naming convention style. by [@wu-clan](https://github.com/wu-clan) in [#98](https://github.com/fastapi-practices/fastapi_best_architecture/pull/98) -* Add sync to async decorator support by [@wu-clan](https://github.com/wu-clan) in [#96](https://github.com/fastapi-practices/fastapi_best_architecture/pull/96) -* Add department-related interfaces and others by [@wu-clan](https://github.com/wu-clan) in [#101](https://github.com/fastapi-practices/fastapi_best_architecture/pull/101) -* Remove useless jwt role_ids by [@downdawn](https://github.com/downdawn) in [#103](https://github.com/fastapi-practices/fastapi_best_architecture/pull/103) -* Add departmental status authentication by [@wu-clan](https://github.com/wu-clan) in [#104](https://github.com/fastapi-practices/fastapi_best_architecture/pull/104) -* Add casbine-related interfaces by [@wu-clan](https://github.com/wu-clan) in [#107](https://github.com/fastapi-practices/fastapi_best_architecture/pull/107) -* Replace aioredis to redis. by [@wu-clan](https://github.com/wu-clan) in [#108](https://github.com/fastapi-practices/fastapi_best_architecture/pull/108) -* opera_log_middleware method split by [@downdawn](https://github.com/downdawn) in [#105](https://github.com/fastapi-practices/fastapi_best_architecture/pull/105) -* Fix offline parse ip info by [@wu-clan](https://github.com/wu-clan) in [#112](https://github.com/fastapi-practices/fastapi_best_architecture/pull/112) -* Update the README document by [@wu-clan](https://github.com/wu-clan) in [#113](https://github.com/fastapi-practices/fastapi_best_architecture/pull/113) -* Update development process suggestions by [@wu-clan](https://github.com/wu-clan) in [#114](https://github.com/fastapi-practices/fastapi_best_architecture/pull/114) -* Fix log table msg field length by [@wu-clan](https://github.com/wu-clan) in [#117](https://github.com/fastapi-practices/fastapi_best_architecture/pull/117) -* Add menu-related interfaces by [@wu-clan](https://github.com/wu-clan) in [#118](https://github.com/fastapi-practices/fastapi_best_architecture/pull/118) -* Omitting table names from the autogenerate process by [@downdawn](https://github.com/downdawn) in [#125](https://github.com/fastapi-practices/fastapi_best_architecture/pull/125) -* Add login graphic captcha by [@wu-clan](https://github.com/wu-clan) in [#124](https://github.com/fastapi-practices/fastapi_best_architecture/pull/124) -* fix the operation log storage exception by [@wu-clan](https://github.com/wu-clan) in [#130](https://github.com/fastapi-practices/fastapi_best_architecture/pull/130) -* add dictionary management interface by [@downdawn](https://github.com/downdawn) in [#127](https://github.com/fastapi-practices/fastapi_best_architecture/pull/127) -* Update and fix permissions logic by [@wu-clan](https://github.com/wu-clan) in [#129](https://github.com/fastapi-practices/fastapi_best_architecture/pull/129) -* Update JWT status detection by [@wu-clan](https://github.com/wu-clan) in [#133](https://github.com/fastapi-practices/fastapi_best_architecture/pull/133) -* The level field is deprecated but remained by [@wu-clan](https://github.com/wu-clan) in [#134](https://github.com/fastapi-practices/fastapi_best_architecture/pull/134) -* Add system monitoring interface by [@wu-clan](https://github.com/wu-clan) in [#135](https://github.com/fastapi-practices/fastapi_best_architecture/pull/135) -* Fix the operation log message error by [@wu-clan](https://github.com/wu-clan) in [#140](https://github.com/fastapi-practices/fastapi_best_architecture/pull/140) -* Update the server monitoring interface by [@wu-clan](https://github.com/wu-clan) in [#141](https://github.com/fastapi-practices/fastapi_best_architecture/pull/141) -* Update the status field type to int by [@wu-clan](https://github.com/wu-clan) in [#143](https://github.com/fastapi-practices/fastapi_best_architecture/pull/143) -* Fix the operation log field type error by [@wu-clan](https://github.com/wu-clan) in [#145](https://github.com/fastapi-practices/fastapi_best_architecture/pull/145) -* Fix the exception handler HTTPException type error by [@wu-clan](https://github.com/wu-clan) in [#146](https://github.com/fastapi-practices/fastapi_best_architecture/pull/146) -* Add the schema base class by [@wu-clan](https://github.com/wu-clan) in [#148](https://github.com/fastapi-practices/fastapi_best_architecture/pull/148) -* Add datetime util by [@wu-clan](https://github.com/wu-clan) in [#149](https://github.com/fastapi-practices/fastapi_best_architecture/pull/149) -* Fix permitted exception. by [@downdawn](https://github.com/downdawn) in [#151](https://github.com/fastapi-practices/fastapi_best_architecture/pull/151) -* Refactor global datetime to timezone datetime by [@wu-clan](https://github.com/wu-clan) in [#152](https://github.com/fastapi-practices/fastapi_best_architecture/pull/152) -* Add processing after password reset by [@wu-clan](https://github.com/wu-clan) in [#154](https://github.com/fastapi-practices/fastapi_best_architecture/pull/154) -* Update some routing groups by [@wu-clan](https://github.com/wu-clan) in [#155](https://github.com/fastapi-practices/fastapi_best_architecture/pull/155) -* Add task-related interfaces by [@wu-clan](https://github.com/wu-clan) in [#157](https://github.com/fastapi-practices/fastapi_best_architecture/pull/157) -* Update the instructions in the readme by [@wu-clan](https://github.com/wu-clan) in [#159](https://github.com/fastapi-practices/fastapi_best_architecture/pull/159) -* Update some interface permission checks by [@wu-clan](https://github.com/wu-clan) in [#158](https://github.com/fastapi-practices/fastapi_best_architecture/pull/158) -* Add database init sql files by [@wu-clan](https://github.com/wu-clan) in [#160](https://github.com/fastapi-practices/fastapi_best_architecture/pull/160) -* Adapt to frontend by [@downdawn](https://github.com/downdawn) in [#162](https://github.com/fastapi-practices/fastapi_best_architecture/pull/162) -* Update menu handling logic by [@wu-clan](https://github.com/wu-clan) in [#163](https://github.com/fastapi-practices/fastapi_best_architecture/pull/163) -* Bump fastapi from 0.95.2 to 0.99.0 by [@wu-clan](https://github.com/wu-clan) in [#164](https://github.com/fastapi-practices/fastapi_best_architecture/pull/164) -* Enable login interface captcha function by [@wu-clan](https://github.com/wu-clan) in [#165](https://github.com/fastapi-practices/fastapi_best_architecture/pull/165) -* Fix CORS 500 status code exception by [@wu-clan](https://github.com/wu-clan) in [#167](https://github.com/fastapi-practices/fastapi_best_architecture/pull/167) -* Add menu table title field by [@wu-clan](https://github.com/wu-clan) in [#170](https://github.com/fastapi-practices/fastapi_best_architecture/pull/170) -* fix tree data algorithms exception by [@downdawn](https://github.com/downdawn) in [#169](https://github.com/fastapi-practices/fastapi_best_architecture/pull/169) -* Fix the menu query children exception by [@wu-clan](https://github.com/wu-clan) in [#171](https://github.com/fastapi-practices/fastapi_best_architecture/pull/171) -* Custom request rate limit callback function by [@wu-clan](https://github.com/wu-clan) in [#174](https://github.com/fastapi-practices/fastapi_best_architecture/pull/174) -* Add demo site mode by [@wu-clan](https://github.com/wu-clan) in [#173](https://github.com/fastapi-practices/fastapi_best_architecture/pull/173) -* Add query users by department ID by [@wu-clan](https://github.com/wu-clan) in [#175](https://github.com/fastapi-practices/fastapi_best_architecture/pull/175) -* Update monitoring return data by [@wu-clan](https://github.com/wu-clan) in [#176](https://github.com/fastapi-practices/fastapi_best_architecture/pull/176) -* Update user role interface to standalone by [@wu-clan](https://github.com/wu-clan) in [#177](https://github.com/fastapi-practices/fastapi_best_architecture/pull/177) -* Add get roles related interface by [@wu-clan](https://github.com/wu-clan) in [#178](https://github.com/fastapi-practices/fastapi_best_architecture/pull/178) -* Add the role status conditional query by [@wu-clan](https://github.com/wu-clan) in [#181](https://github.com/fastapi-practices/fastapi_best_architecture/pull/181) -* Update role menu interface is standalone by [@wu-clan](https://github.com/wu-clan) in [#182](https://github.com/fastapi-practices/fastapi_best_architecture/pull/182) -* Add interface to get all menus of a role by [@wu-clan](https://github.com/wu-clan) in [#183](https://github.com/fastapi-practices/fastapi_best_architecture/pull/183) -* Fix schema enum condition exception by [@wu-clan](https://github.com/wu-clan) in [#185](https://github.com/fastapi-practices/fastapi_best_architecture/pull/185) -* Bump Async SQLAlchemy Adapter from 1.1.0 to 1.2.0 by [@wu-clan](https://github.com/wu-clan) in [#187](https://github.com/fastapi-practices/fastapi_best_architecture/pull/187) -* Bump cryptography from 41.0.0 to 41.0.2 by [@dependabot](https://github.com/dependabot) in [#179](https://github.com/fastapi-practices/fastapi_best_architecture/pull/179) -* Update SQL files and use them as execution targets by [@wu-clan](https://github.com/wu-clan) in [#188](https://github.com/fastapi-practices/fastapi_best_architecture/pull/188) -* Add user password encryption salt by [@wu-clan](https://github.com/wu-clan) in [#191](https://github.com/fastapi-practices/fastapi_best_architecture/pull/191) -* Update roles and nickname fields to be optiona by [@wu-clan](https://github.com/wu-clan) in [#190](https://github.com/fastapi-practices/fastapi_best_architecture/pull/190) -* Fix casbin async enforcer by [@wu-clan](https://github.com/wu-clan) in [#192](https://github.com/fastapi-practices/fastapi_best_architecture/pull/192) -* Add more Casbin related interfaces by [@wu-clan](https://github.com/wu-clan) in [#195](https://github.com/fastapi-practices/fastapi_best_architecture/pull/195) -* Update the nickname field creation logic by [@wu-clan](https://github.com/wu-clan) in [#196](https://github.com/fastapi-practices/fastapi_best_architecture/pull/196) -* Update the Casbin model matcher rules by [@wu-clan](https://github.com/wu-clan) in [#197](https://github.com/fastapi-practices/fastapi_best_architecture/pull/197) -* Add api and casbin related interfaces by [@wu-clan](https://github.com/wu-clan) in [#198](https://github.com/fastapi-practices/fastapi_best_architecture/pull/198) -* Update asynccasbin to casbin async api by [@wu-clan](https://github.com/wu-clan) in [#199](https://github.com/fastapi-practices/fastapi_best_architecture/pull/199) -* Fix the interface logic for dept details by [@wu-clan](https://github.com/wu-clan) in [#201](https://github.com/fastapi-practices/fastapi_best_architecture/pull/201) -* Add ItsDangerous request parameters encryption by [@wu-clan](https://github.com/wu-clan) in [#203](https://github.com/fastapi-practices/fastapi_best_architecture/pull/203) -* Add jwt login whitelist by [@downdawn](https://github.com/downdawn) in [#204](https://github.com/fastapi-practices/fastapi_best_architecture/pull/204) -* Add ip location cache by [@downdawn](https://github.com/downdawn) in [#205](https://github.com/fastapi-practices/fastapi_best_architecture/pull/205) -* Fix int enum class inheritance by [@wu-clan](https://github.com/wu-clan) in [#208](https://github.com/fastapi-practices/fastapi_best_architecture/pull/208) -* Fix the task interface return data by [@wu-clan](https://github.com/wu-clan) in [#215](https://github.com/fastapi-practices/fastapi_best_architecture/pull/215) -* Update the README document by [@wu-clan](https://github.com/wu-clan) in [#214](https://github.com/fastapi-practices/fastapi_best_architecture/pull/214) -* Fix token whitelist and new token storage by [@wu-clan](https://github.com/wu-clan) in [#220](https://github.com/fastapi-practices/fastapi_best_architecture/pull/220) -* Optimize role menu authorization logic by [@wu-clan](https://github.com/wu-clan) in [#221](https://github.com/fastapi-practices/fastapi_best_architecture/pull/221) -* Simplified query interface returns data serialization by [@wu-clan](https://github.com/wu-clan) in [#219](https://github.com/fastapi-practices/fastapi_best_architecture/pull/219) -* Update the global unified response code by [@wu-clan](https://github.com/wu-clan) in [#223](https://github.com/fastapi-practices/fastapi_best_architecture/pull/223) -* Fix global unknown exception return by [@wu-clan](https://github.com/wu-clan) in [#224](https://github.com/fastapi-practices/fastapi_best_architecture/pull/224) -* Update the pytz library to zoneinfo by [@wu-clan](https://github.com/wu-clan) in [#226](https://github.com/fastapi-practices/fastapi_best_architecture/pull/226) -* Add token decoding expiration exception by [@wu-clan](https://github.com/wu-clan) in [#227](https://github.com/fastapi-practices/fastapi_best_architecture/pull/227) -* Fix the task run method and data type by [@wu-clan](https://github.com/wu-clan) in [#228](https://github.com/fastapi-practices/fastapi_best_architecture/pull/228) -* Remove the NoReturn return type by [@wu-clan](https://github.com/wu-clan) in [#232](https://github.com/fastapi-practices/fastapi_best_architecture/pull/232) -* Add init pytest data sql file by [@wu-clan](https://github.com/wu-clan) in [#231](https://github.com/fastapi-practices/fastapi_best_architecture/pull/231) -* Fix pytest interface unit tests by [@wu-clan](https://github.com/wu-clan) in [#233](https://github.com/fastapi-practices/fastapi_best_architecture/pull/233) -* Replace APScheduler to Celery asynchronous tasks by [@wu-clan](https://github.com/wu-clan) in [#229](https://github.com/fastapi-practices/fastapi_best_architecture/pull/229) -* Fix the conflict between Access and OperaLog middleware by [@wu-clan](https://github.com/wu-clan) in [#236](https://github.com/fastapi-practices/fastapi_best_architecture/pull/236) -* Fix unregistered error received when celery call task by [@wu-clan](https://github.com/wu-clan) in [#239](https://github.com/fastapi-practices/fastapi_best_architecture/pull/239) -* Fix database engine UUID type compatibility by [@wu-clan](https://github.com/wu-clan) in [#241](https://github.com/fastapi-practices/fastapi_best_architecture/pull/241) -* adopt ruff formatter by [@wu-clan](https://github.com/wu-clan) in [#242](https://github.com/fastapi-practices/fastapi_best_architecture/pull/242) -* Bump cryptography from 41.0.2 to 41.0.6 by [@dependabot](https://github.com/dependabot) in [#243](https://github.com/fastapi-practices/fastapi_best_architecture/pull/243) -* Add a telegram interactive link by [@wu-clan](https://github.com/wu-clan) in [#245](https://github.com/fastapi-practices/fastapi_best_architecture/pull/245) -* Fix validation error log code return type by [@wu-clan](https://github.com/wu-clan) in [#247](https://github.com/fastapi-practices/fastapi_best_architecture/pull/247) -* Fix refresh token interface user type format by [@wu-clan](https://github.com/wu-clan) in [#248](https://github.com/fastapi-practices/fastapi_best_architecture/pull/248) -* Optimize operation log code type logic by [@wu-clan](https://github.com/wu-clan) in [#249](https://github.com/fastapi-practices/fastapi_best_architecture/pull/249) -* Fix get all G rules interface logic by [@wu-clan](https://github.com/wu-clan) in [#250](https://github.com/fastapi-practices/fastapi_best_architecture/pull/250) -* Simplify the multivariate expression of exceptions handler by [@wu-clan](https://github.com/wu-clan) in [#252](https://github.com/fastapi-practices/fastapi_best_architecture/pull/252) -* Fix exception handler parameter call by [@wu-clan](https://github.com/wu-clan) in [#253](https://github.com/fastapi-practices/fastapi_best_architecture/pull/253) -* Prepare to lock the pydantic-v1 branch by [@wu-clan](https://github.com/wu-clan) in [#254](https://github.com/fastapi-practices/fastapi_best_architecture/pull/254) -* Add a stand-alone assertion error handler by [@wu-clan](https://github.com/wu-clan) in [#255](https://github.com/fastapi-practices/fastapi_best_architecture/pull/255) -* Clean up todo and fix typo by [@wu-clan](https://github.com/wu-clan) in [#256](https://github.com/fastapi-practices/fastapi_best_architecture/pull/256) -* Migrate to pydantic-v2 by [@wu-clan](https://github.com/wu-clan) in [#246](https://github.com/fastapi-practices/fastapi_best_architecture/pull/246) -* Add pydantic-v2 migration reminder by [@wu-clan](https://github.com/wu-clan) in [#257](https://github.com/fastapi-practices/fastapi_best_architecture/pull/257) -* Add the project status page to the README by [@wu-clan](https://github.com/wu-clan) in [#259](https://github.com/fastapi-practices/fastapi_best_architecture/pull/259) -* Clean up outdated pydantic dict methods by [@wu-clan](https://github.com/wu-clan) in [#262](https://github.com/fastapi-practices/fastapi_best_architecture/pull/262) -* Fix use request.form() in middleware by [@wu-clan](https://github.com/wu-clan) in [#260](https://github.com/fastapi-practices/fastapi_best_architecture/pull/260) -* Reconstruct RBAC authentication logic by [@wu-clan](https://github.com/wu-clan) in [#264](https://github.com/fastapi-practices/fastapi_best_architecture/pull/264) -* Attempt to optimize serialization performance by [@wu-clan](https://github.com/wu-clan) in [#266](https://github.com/fastapi-practices/fastapi_best_architecture/pull/266) -* Update schemas naming style by [@wu-clan](https://github.com/wu-clan) in [#272](https://github.com/fastapi-practices/fastapi_best_architecture/pull/272) -* Update sponsor links and FUNDING by [@wu-clan](https://github.com/wu-clan) in [#273](https://github.com/fastapi-practices/fastapi_best_architecture/pull/273) -* Fix dept and menu parent id update logic by [@wu-clan](https://github.com/wu-clan) in [#274](https://github.com/fastapi-practices/fastapi_best_architecture/pull/274) -* Update interface coding style by [@wu-clan](https://github.com/wu-clan) in [#275](https://github.com/fastapi-practices/fastapi_best_architecture/pull/275) -* Update dao and service instantiation styles by [@wu-clan](https://github.com/wu-clan) in [#276](https://github.com/fastapi-practices/fastapi_best_architecture/pull/276) -* Add custom email string type by [@wu-clan](https://github.com/wu-clan) in [#277](https://github.com/fastapi-practices/fastapi_best_architecture/pull/277) -* Fix custom validator exception serialization in dev mode by [@wu-clan](https://github.com/wu-clan) in [#278](https://github.com/fastapi-practices/fastapi_best_architecture/pull/278) -* Restore the Github ci workflows by [@wu-clan](https://github.com/wu-clan) in [#281](https://github.com/fastapi-practices/fastapi_best_architecture/pull/281) -* Add the pdm project manager by [@wu-clan](https://github.com/wu-clan) in [#282](https://github.com/fastapi-practices/fastapi_best_architecture/pull/282) -* Add the front-end docker-compose script by [@wu-clan](https://github.com/wu-clan) in [#283](https://github.com/fastapi-practices/fastapi_best_architecture/pull/283) -* Update the response status code in exception handlers by [@wu-clan](https://github.com/wu-clan) in [#292](https://github.com/fastapi-practices/fastapi_best_architecture/pull/292) -* Update interface file directory level by [@wu-clan](https://github.com/wu-clan) in [#295](https://github.com/fastapi-practices/fastapi_best_architecture/pull/295) -* Add the repository star map by [@wu-clan](https://github.com/wu-clan) in [#296](https://github.com/fastapi-practices/fastapi_best_architecture/pull/296) -* Add OAuth 2.0 authorization login by [@wu-clan](https://github.com/wu-clan) in [#293](https://github.com/fastapi-practices/fastapi_best_architecture/pull/293) -* Prepare to lock the legacy branch by [@wu-clan](https://github.com/wu-clan) in [#301](https://github.com/fastapi-practices/fastapi_best_architecture/pull/301) -* Update the README.md branch prompt by [@wu-clan](https://github.com/wu-clan) in [#302](https://github.com/fastapi-practices/fastapi_best_architecture/pull/302) -* Refactor the backend architecture by [@wu-clan](https://github.com/wu-clan) in [#299](https://github.com/fastapi-practices/fastapi_best_architecture/pull/299) -* Fix English README.md update date by [@wu-clan](https://github.com/wu-clan) in [#308](https://github.com/fastapi-practices/fastapi_best_architecture/pull/308) -* Add backend scripts description by [@wu-clan](https://github.com/wu-clan) in [#309](https://github.com/fastapi-practices/fastapi_best_architecture/pull/309) -* Fix missing data from alembic migration by [@wu-clan](https://github.com/wu-clan) in [#312](https://github.com/fastapi-practices/fastapi_best_architecture/pull/312) -* Update CRUDBase to sqlalchemy-crud-plus by [@wu-clan](https://github.com/wu-clan) in [#317](https://github.com/fastapi-practices/fastapi_best_architecture/pull/317) -* Upgrade and update fastapi service startup by [@wu-clan](https://github.com/wu-clan) in [#319](https://github.com/fastapi-practices/fastapi_best_architecture/pull/319) -* Delete the gzip middleware to improve performance by [@wu-clan](https://github.com/wu-clan) in [#325](https://github.com/fastapi-practices/fastapi_best_architecture/pull/325) -* Add interface fast response method by [@wu-clan](https://github.com/wu-clan) in [#327](https://github.com/fastapi-practices/fastapi_best_architecture/pull/327) -* Update the opera log middleware task by [@wu-clan](https://github.com/wu-clan) in [#326](https://github.com/fastapi-practices/fastapi_best_architecture/pull/326) -* Add test account to README by [@wu-clan](https://github.com/wu-clan) in [#330](https://github.com/fastapi-practices/fastapi_best_architecture/pull/330) -* Restore the main startup of fastapi service by [@wu-clan](https://github.com/wu-clan) in [#336](https://github.com/fastapi-practices/fastapi_best_architecture/pull/336) -* Update app route definition rules by [@wu-clan](https://github.com/wu-clan) in [#341](https://github.com/fastapi-practices/fastapi_best_architecture/pull/341) -* Add Linux Do OAuth2 login by [@wu-clan](https://github.com/wu-clan) in [#343](https://github.com/fastapi-practices/fastapi_best_architecture/pull/343) -* Fix user social binding query by [@wu-clan](https://github.com/wu-clan) in [#344](https://github.com/fastapi-practices/fastapi_best_architecture/pull/344) -* Refactor global log default handler by [@obrua](https://github.com/obrua) in [#347](https://github.com/fastapi-practices/fastapi_best_architecture/pull/347) -* Add code generator app by [@wu-clan](https://github.com/wu-clan) in [#318](https://github.com/fastapi-practices/fastapi_best_architecture/pull/318) -* Update and enable access log middleware by [@wu-clan](https://github.com/wu-clan) in [#348](https://github.com/fastapi-practices/fastapi_best_architecture/pull/348) -* Add code generator README document by [@wu-clan](https://github.com/wu-clan) in [#349](https://github.com/fastapi-practices/fastapi_best_architecture/pull/349) -* Fix model template conditional syntax by [@wu-clan](https://github.com/wu-clan) in [#351](https://github.com/fastapi-practices/fastapi_best_architecture/pull/351) -* Update code generation model column type storage by [@wu-clan](https://github.com/wu-clan) in [#352](https://github.com/fastapi-practices/fastapi_best_architecture/pull/352) -* Fix gen model and schema template formatting by [@wu-clan](https://github.com/wu-clan) in [#356](https://github.com/fastapi-practices/fastapi_best_architecture/pull/356) -* Add code generator to create init files by [@wu-clan](https://github.com/wu-clan) in [#358](https://github.com/fastapi-practices/fastapi_best_architecture/pull/358) -* Fix alembic migration failure caused by model by [@wu-clan](https://github.com/wu-clan) in [#359](https://github.com/fastapi-practices/fastapi_best_architecture/pull/359) -* Update the docker-compose deployment script by [@wu-clan](https://github.com/wu-clan) in [#360](https://github.com/fastapi-practices/fastapi_best_architecture/pull/360) -* Update oauth2 route naming and return by [@wu-clan](https://github.com/wu-clan) in [#361](https://github.com/fastapi-practices/fastapi_best_architecture/pull/361) -* Bump fast captcha version to 0.3.2 by [@wu-clan](https://github.com/wu-clan) in [#362](https://github.com/fastapi-practices/fastapi_best_architecture/pull/362) -* Update crud user staff field logic by [@wu-clan](https://github.com/wu-clan) in [#363](https://github.com/fastapi-practices/fastapi_best_architecture/pull/363) -* Fix code auto-generated model creation by [@wu-clan](https://github.com/wu-clan) in [#364](https://github.com/fastapi-practices/fastapi_best_architecture/pull/364) -* Add page to display dynamic configuration by [@wu-clan](https://github.com/wu-clan) in [#365](https://github.com/fastapi-practices/fastapi_best_architecture/pull/365) -* Fix celery asynchronous task execution by [@wu-clan](https://github.com/wu-clan) in [#367](https://github.com/fastapi-practices/fastapi_best_architecture/pull/367) -* Update operation log middleware info reading by [@wu-clan](https://github.com/wu-clan) in [#368](https://github.com/fastapi-practices/fastapi_best_architecture/pull/368) -* Update create new token function return type by [@wu-clan](https://github.com/wu-clan) in [#369](https://github.com/fastapi-practices/fastapi_best_architecture/pull/369) -* Update access log cost time style by [@wu-clan](https://github.com/wu-clan) in [#370](https://github.com/fastapi-practices/fastapi_best_architecture/pull/370) -* Update code generate business model time column by [@wu-clan](https://github.com/wu-clan) in [#371](https://github.com/fastapi-practices/fastapi_best_architecture/pull/371) -* Add custom code template pathname config by [@wu-clan](https://github.com/wu-clan) in [#372](https://github.com/fastapi-practices/fastapi_best_architecture/pull/372) -* Update some code generation api and params by [@wu-clan](https://github.com/wu-clan) in [#373](https://github.com/fastapi-practices/fastapi_best_architecture/pull/373) -* Update code generate download api auth by [@wu-clan](https://github.com/wu-clan) in [#376](https://github.com/fastapi-practices/fastapi_best_architecture/pull/376) -* Update current menu status auth by [@wu-clan](https://github.com/wu-clan) in [#374](https://github.com/fastapi-practices/fastapi_best_architecture/pull/374) -* Fix code generation model create and update by [@wu-clan](https://github.com/wu-clan) in [#378](https://github.com/fastapi-practices/fastapi_best_architecture/pull/378) -* Update user and auth error message by [@wu-clan](https://github.com/wu-clan) in [#379](https://github.com/fastapi-practices/fastapi_best_architecture/pull/379) -* Add directory tree and update app notes by [@wu-clan](https://github.com/wu-clan) in [#380](https://github.com/fastapi-practices/fastapi_best_architecture/pull/380) -* Optimize serialization and jwt performance by [@wu-clan](https://github.com/wu-clan) in [#382](https://github.com/fastapi-practices/fastapi_best_architecture/pull/382) -* Fix arm system cpu frequency retrieval by [@yshan2028](https://github.com/yshan2028) in [#385](https://github.com/fastapi-practices/fastapi_best_architecture/pull/385) -* Fix logging when a login error occurs by [@wu-clan](https://github.com/wu-clan) in [#386](https://github.com/fastapi-practices/fastapi_best_architecture/pull/386) -* Update redis cache prefix separator to `:` by [@wu-clan](https://github.com/wu-clan) in [#387](https://github.com/fastapi-practices/fastapi_best_architecture/pull/387) -* Bump sqlalchemy crud plus version to 1.3.0 by [@wu-clan](https://github.com/wu-clan) in [#388](https://github.com/fastapi-practices/fastapi_best_architecture/pull/388) -* Update the README announcement to note by [@wu-clan](https://github.com/wu-clan) in [#390](https://github.com/fastapi-practices/fastapi_best_architecture/pull/390) -* Fix code generation to new features by [@wu-clan](https://github.com/wu-clan) in [#393](https://github.com/fastapi-practices/fastapi_best_architecture/pull/393) -* Fix OAuth2 user query conditions by [@wu-clan](https://github.com/wu-clan) in [#396](https://github.com/fastapi-practices/fastapi_best_architecture/pull/396) -* Fix the user permissions update services by [@wu-clan](https://github.com/wu-clan) in [#397](https://github.com/fastapi-practices/fastapi_best_architecture/pull/397) -* Update code generate comment column format by [@wu-clan](https://github.com/wu-clan) in [#399](https://github.com/fastapi-practices/fastapi_best_architecture/pull/399) -* Update the interactive link address by [@wu-clan](https://github.com/wu-clan) in [#402](https://github.com/fastapi-practices/fastapi_best_architecture/pull/402) -* Updated refresh token storage logic by [@wu-clan](https://github.com/wu-clan) in [#403](https://github.com/fastapi-practices/fastapi_best_architecture/pull/403) -* Fix server monitor io blocking by [@wu-clan](https://github.com/wu-clan) in [#404](https://github.com/fastapi-practices/fastapi_best_architecture/pull/404) -* Fix cookie expiration time zone by [@wu-clan](https://github.com/wu-clan) in [#408](https://github.com/fastapi-practices/fastapi_best_architecture/pull/408) -* Add request trace ID record by [@wu-clan](https://github.com/wu-clan) in [#409](https://github.com/fastapi-practices/fastapi_best_architecture/pull/409) -* Optimize the naming of setting params by [@wu-clan](https://github.com/wu-clan) in [#410](https://github.com/fastapi-practices/fastapi_best_architecture/pull/410) -* Add trace ID to exception handlers by [@wu-clan](https://github.com/wu-clan) in [#411](https://github.com/fastapi-practices/fastapi_best_architecture/pull/411) -* Update the global exception log stack by [@wu-clan](https://github.com/wu-clan) in [#406](https://github.com/fastapi-practices/fastapi_best_architecture/pull/406) -* Bump pydantic from 2.8.1 to 2.9.1 by [@wu-clan](https://github.com/wu-clan) in [#412](https://github.com/fastapi-practices/fastapi_best_architecture/pull/412) -* Optimize exception info opera log record by [@wu-clan](https://github.com/wu-clan) in [#413](https://github.com/fastapi-practices/fastapi_best_architecture/pull/413) -* Fix log output and logging levels by [@wu-clan](https://github.com/wu-clan) in [#414](https://github.com/fastapi-practices/fastapi_best_architecture/pull/414) -* Fix exception logging in opera log by [@wu-clan](https://github.com/wu-clan) in [#417](https://github.com/fastapi-practices/fastapi_best_architecture/pull/417) -* Fix the gen model template formatting by [@wu-clan](https://github.com/wu-clan) in [#416](https://github.com/fastapi-practices/fastapi_best_architecture/pull/416) -* Optimize the internal implementation of serializers by [@wu-clan](https://github.com/wu-clan) in [#419](https://github.com/fastapi-practices/fastapi_best_architecture/pull/419) -* Fix for create new token cache delete by [@wu-clan](https://github.com/wu-clan) in [#420](https://github.com/fastapi-practices/fastapi_best_architecture/pull/420) -* Update JWT errors class import by [@wu-clan](https://github.com/wu-clan) in [#421](https://github.com/fastapi-practices/fastapi_best_architecture/pull/421) -* Update multi login sync update refresh tokens by [@wu-clan](https://github.com/wu-clan) in [#422](https://github.com/fastapi-practices/fastapi_best_architecture/pull/422) -* Update sync function calls in JWT by [@wu-clan](https://github.com/wu-clan) in [#423](https://github.com/fastapi-practices/fastapi_best_architecture/pull/423) -* Fix the missing OAuth2 interface parameters by [@wu-clan](https://github.com/wu-clan) in [#425](https://github.com/fastapi-practices/fastapi_best_architecture/pull/425) -* Add request state middleware by [@wu-clan](https://github.com/wu-clan) in [#426](https://github.com/fastapi-practices/fastapi_best_architecture/pull/426) -* Fix pydantic field and model validator by [@wu-clan](https://github.com/wu-clan) in [#427](https://github.com/fastapi-practices/fastapi_best_architecture/pull/427) -* Fix the OAuth2 service login log task by [@wu-clan](https://github.com/wu-clan) in [#428](https://github.com/fastapi-practices/fastapi_best_architecture/pull/428) -* Update official documentation link to README by [@wu-clan](https://github.com/wu-clan) in [#429](https://github.com/fastapi-practices/fastapi_best_architecture/pull/429) -* Optimize and normalize the code generator by [@wu-clan](https://github.com/wu-clan) in [#430](https://github.com/fastapi-practices/fastapi_best_architecture/pull/430) -* Bump redis from 5.0.1 to 5.1.0 by [@wu-clan](https://github.com/wu-clan) in [#433](https://github.com/fastapi-practices/fastapi_best_architecture/pull/433) -* Update interactive link descriptions by [@wu-clan](https://github.com/wu-clan) in [#434](https://github.com/fastapi-practices/fastapi_best_architecture/pull/434) -* Optimize the serialize return of SQLA select by [@wu-clan](https://github.com/wu-clan) in [#436](https://github.com/fastapi-practices/fastapi_best_architecture/pull/436) -* Update project manager pdm to uv by [@wu-clan](https://github.com/wu-clan) in [#440](https://github.com/fastapi-practices/fastapi_best_architecture/pull/440) -* Add asynchronous socketio application server by [@wu-clan](https://github.com/wu-clan) in [#437](https://github.com/fastapi-practices/fastapi_best_architecture/pull/437) -* Add dependency-groups by PEP 735 by [@wu-clan](https://github.com/wu-clan) in [#444](https://github.com/fastapi-practices/fastapi_best_architecture/pull/444) -* Update the usage documentation in README by [@wu-clan](https://github.com/wu-clan) in [#449](https://github.com/fastapi-practices/fastapi_best_architecture/pull/449) -* Bump sqlalchemy crud plus version to 1.5.0 by [@wu-clan](https://github.com/wu-clan) in [#450](https://github.com/fastapi-practices/fastapi_best_architecture/pull/450) -* Update singleton pattern class typing by [@wu-clan](https://github.com/wu-clan) in [#452](https://github.com/fastapi-practices/fastapi_best_architecture/pull/452) -* Update system config to be dynamic by [@wu-clan](https://github.com/wu-clan) in [#447](https://github.com/fastapi-practices/fastapi_best_architecture/pull/447) -* Update multiple version dependency specifiers by [@wu-clan](https://github.com/wu-clan) in [#454](https://github.com/fastapi-practices/fastapi_best_architecture/pull/454) -* Fix typo in contribution description by [@wu-clan](https://github.com/wu-clan) in [#456](https://github.com/fastapi-practices/fastapi_best_architecture/pull/456) -* Fix code generation file missing by [@wu-clan](https://github.com/wu-clan) in [#457](https://github.com/fastapi-practices/fastapi_best_architecture/pull/457) -* Update the celery configuration and tasks by [@wu-clan](https://github.com/wu-clan) in [#458](https://github.com/fastapi-practices/fastapi_best_architecture/pull/458) -* Update some service class invocations by [@wu-clan](https://github.com/wu-clan) in [#459](https://github.com/fastapi-practices/fastapi_best_architecture/pull/459) -* Update code generator API file structure by [@wu-clan](https://github.com/wu-clan) in [#460](https://github.com/fastapi-practices/fastapi_best_architecture/pull/460) -* Update api body params to schema by [@wu-clan](https://github.com/wu-clan) in [#461](https://github.com/fastapi-practices/fastapi_best_architecture/pull/461) -* Fix celery service functions error by [@wu-clan](https://github.com/wu-clan) in [#462](https://github.com/fastapi-practices/fastapi_best_architecture/pull/462) -* Update user password encryption method by [@wu-clan](https://github.com/wu-clan) in [#463](https://github.com/fastapi-practices/fastapi_best_architecture/pull/463) -* Update role-based data permissions by [@wu-clan](https://github.com/wu-clan) in [#465](https://github.com/fastapi-practices/fastapi_best_architecture/pull/465) -* Bump tornado from 6.4.1 to 6.4.2 in /backend by [@dependabot](https://github.com/dependabot) in [#466](https://github.com/fastapi-practices/fastapi_best_architecture/pull/466) -* Fix schema type of user role rule by [@wu-clan](https://github.com/wu-clan) in [#467](https://github.com/fastapi-practices/fastapi_best_architecture/pull/467) -* Simplify data rule and remove type by [@wu-clan](https://github.com/wu-clan) in [#468](https://github.com/fastapi-practices/fastapi_best_architecture/pull/468) -* Add the project logo to README by [@wu-clan](https://github.com/wu-clan) in [#469](https://github.com/fastapi-practices/fastapi_best_architecture/pull/469) -* Optimized user auth for auth service by [@wu-clan](https://github.com/wu-clan) in [#472](https://github.com/fastapi-practices/fastapi_best_architecture/pull/472) -* Fix data rule expression column comment by [@wu-clan](https://github.com/wu-clan) in [#473](https://github.com/fastapi-practices/fastapi_best_architecture/pull/473) -* Fix and update alembic env and ini by [@wu-clan](https://github.com/wu-clan) in [#474](https://github.com/fastapi-practices/fastapi_best_architecture/pull/474) -* Fix login log parameter error in task by [@wu-clan](https://github.com/wu-clan) in [#476](https://github.com/fastapi-practices/fastapi_best_architecture/pull/476) -* Remove data scope in the role model by [@wu-clan](https://github.com/wu-clan) in [#478](https://github.com/fastapi-practices/fastapi_best_architecture/pull/478) -* Add postgresql database support by [@Meepoljdx](https://github.com/Meepoljdx) in [#475](https://github.com/fastapi-practices/fastapi_best_architecture/pull/475) -* Update opera log cost time precision by [@wu-clan](https://github.com/wu-clan) in [#479](https://github.com/fastapi-practices/fastapi_best_architecture/pull/479) -* Update opera middleware request args parse by [@wu-clan](https://github.com/wu-clan) in [#481](https://github.com/fastapi-practices/fastapi_best_architecture/pull/481) -* Bump msgspec from 0.18.6 to 0.19.0 by [@wu-clan](https://github.com/wu-clan) in [#482](https://github.com/fastapi-practices/fastapi_best_architecture/pull/482) -* Fix user cache when updated user role by [@wu-clan](https://github.com/wu-clan) in [#483](https://github.com/fastapi-practices/fastapi_best_architecture/pull/483) -* Update the route version define location by [@wu-clan](https://github.com/wu-clan) in [#485](https://github.com/fastapi-practices/fastapi_best_architecture/pull/485) -* Optimize docker deploy settings and scripts by [@wu-clan](https://github.com/wu-clan) in [#486](https://github.com/fastapi-practices/fastapi_best_architecture/pull/486) -* Add system notice interface by [@dividduang](https://github.com/dividduang) in [#487](https://github.com/fastapi-practices/fastapi_best_architecture/pull/487) -* Add response model include data schema by [@wu-clan](https://github.com/wu-clan) in [#490](https://github.com/fastapi-practices/fastapi_best_architecture/pull/490) -* Update redocs arg and url to redoc by [@wu-clan](https://github.com/wu-clan) in [#493](https://github.com/fastapi-practices/fastapi_best_architecture/pull/493) -* Fix serialization when pagination is empty by [@qhp13654398483](https://github.com/qhp13654398483) in [#491](https://github.com/fastapi-practices/fastapi_best_architecture/pull/491) -* Update return schema of query interface by [@wu-clan](https://github.com/wu-clan) in [#492](https://github.com/fastapi-practices/fastapi_best_architecture/pull/492) -* Add token related interfaces by [@wu-clan](https://github.com/wu-clan) in [#495](https://github.com/fastapi-practices/fastapi_best_architecture/pull/495) -* Fix return schema of user me api by [@wu-clan](https://github.com/wu-clan) in [#497](https://github.com/fastapi-practices/fastapi_best_architecture/pull/497) -* Fix current user info detail schema by [@wu-clan](https://github.com/wu-clan) in [#499](https://github.com/fastapi-practices/fastapi_best_architecture/pull/499) -* Update menu field show to display by [@wu-clan](https://github.com/wu-clan) in [#498](https://github.com/fastapi-practices/fastapi_best_architecture/pull/498) -* Fix casbin policy api return schema by [@wu-clan](https://github.com/wu-clan) in [#500](https://github.com/fastapi-practices/fastapi_best_architecture/pull/500) -* Fix opera log of non-dict request body by [@wu-clan](https://github.com/wu-clan) in [#501](https://github.com/fastapi-practices/fastapi_best_architecture/pull/501) -* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#504](https://github.com/fastapi-practices/fastapi_best_architecture/pull/504) -* Fix the return datetime data encoder by [@wu-clan](https://github.com/wu-clan) in [#505](https://github.com/fastapi-practices/fastapi_best_architecture/pull/505) -* Fix fastapi config variable naming and type by [@wu-clan](https://github.com/wu-clan) in [#506](https://github.com/fastapi-practices/fastapi_best_architecture/pull/506) -* Fix the user pagination api return schema by [@wu-clan](https://github.com/wu-clan) in [#507](https://github.com/fastapi-practices/fastapi_best_architecture/pull/507) -* Fix OAuth2 service register user args by [@wu-clan](https://github.com/wu-clan) in [#508](https://github.com/fastapi-practices/fastapi_best_architecture/pull/508) -* Fix OAuth2 service user last login time by [@wu-clan](https://github.com/wu-clan) in [#509](https://github.com/fastapi-practices/fastapi_best_architecture/pull/509) -* Fix OAuth2 service user last login time by [@wu-clan](https://github.com/wu-clan) in [#510](https://github.com/fastapi-practices/fastapi_best_architecture/pull/510) -* Add plugin system and notice plugin by [@wu-clan](https://github.com/wu-clan) in [#503](https://github.com/fastapi-practices/fastapi_best_architecture/pull/503) -* Delete the threads in gunicorn config by [@wu-clan](https://github.com/wu-clan) in [#512](https://github.com/fastapi-practices/fastapi_best_architecture/pull/512) -* Add plugin requirements auto install functions by [@wu-clan](https://github.com/wu-clan) in [#514](https://github.com/fastapi-practices/fastapi_best_architecture/pull/514) -* Update casbin rbac verify to plugin by [@wu-clan](https://github.com/wu-clan) in [#513](https://github.com/fastapi-practices/fastapi_best_architecture/pull/513) -* Update the logic for create new token by [@wu-clan](https://github.com/wu-clan) in [#516](https://github.com/fastapi-practices/fastapi_best_architecture/pull/516) -* Add local file upload interfaces by [@wu-clan](https://github.com/wu-clan) in [#489](https://github.com/fastapi-practices/fastapi_best_architecture/pull/489) -* Update loguru and deploy log config by [@wu-clan](https://github.com/wu-clan) in [#517](https://github.com/fastapi-practices/fastapi_best_architecture/pull/517) -* Delete the model redundancy level field by [@wu-clan](https://github.com/wu-clan) in [#518](https://github.com/fastapi-practices/fastapi_best_architecture/pull/518) -* Update the built-in features in README by [@wu-clan](https://github.com/wu-clan) in [#519](https://github.com/fastapi-practices/fastapi_best_architecture/pull/519) +* add base code by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#12](https://github.com/fastapi-practices/fastapi_best_architecture/pull/12) +* fix get_user_info func return None [#9](https://github.com/fastapi-practices/fastapi-best-architecture/issues/9) by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#14](https://github.com/fastapi-practices/fastapi_best_architecture/pull/14) +* simplify user apis [#11](https://github.com/fastapi-practices/fastapi-best-architecture/issues/11) by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#18](https://github.com/fastapi-practices/fastapi_best_architecture/pull/18) +* Add environment variable management and fix Pydantic validation error by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#15](https://github.com/fastapi-practices/fastapi_best_architecture/pull/15) +* update the ruff rules and format the code by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#24](https://github.com/fastapi-practices/fastapi_best_architecture/pull/24) +* improving project configuration by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#25](https://github.com/fastapi-practices/fastapi_best_architecture/pull/25) +* update to python3.10 by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#29](https://github.com/fastapi-practices/fastapi_best_architecture/pull/29) +* update Dockerfile and docker-compose.yml by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#31](https://github.com/fastapi-practices/fastapi_best_architecture/pull/31) +* Update the sub routers setting in the subdirectory by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#32](https://github.com/fastapi-practices/fastapi_best_architecture/pull/32) +* add the get project config api by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#33](https://github.com/fastapi-practices/fastapi_best_architecture/pull/33) +* add test code by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#37](https://github.com/fastapi-practices/fastapi_best_architecture/pull/37) +* fix that the data validation global exception handler does not work by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#40](https://github.com/fastapi-practices/fastapi_best_architecture/pull/40) +* Fix the swagger form login structure abnormality by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#46](https://github.com/fastapi-practices/fastapi_best_architecture/pull/46) +* Bump starlette from 0.26.1 to 0.27.0 by [@dependabot](https://github.com/dependabot) in [fastapi-practices/fastapi_best_architecture#48](https://github.com/fastapi-practices/fastapi_best_architecture/pull/48) +* add rbac authorization by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#41](https://github.com/fastapi-practices/fastapi_best_architecture/pull/41) +* Bump fastapi from 0.95.0 to 0.95.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#53](https://github.com/fastapi-practices/fastapi_best_architecture/pull/53) +* Update the uniform return method to success by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#55](https://github.com/fastapi-practices/fastapi_best_architecture/pull/55) +* add token storage and logout by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#57](https://github.com/fastapi-practices/fastapi_best_architecture/pull/57) +* fix jwt parameter parsing error by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#61](https://github.com/fastapi-practices/fastapi_best_architecture/pull/61) +* add token refreshing mechanism by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#62](https://github.com/fastapi-practices/fastapi_best_architecture/pull/62) +* Update uniform return to custom encoder by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#60](https://github.com/fastapi-practices/fastapi_best_architecture/pull/60) +* update token default exception return message by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#65](https://github.com/fastapi-practices/fastapi_best_architecture/pull/65) +* add English and Chinese README jump links by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#66](https://github.com/fastapi-practices/fastapi_best_architecture/pull/66) +* update token refresh expire time rule by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#67](https://github.com/fastapi-practices/fastapi_best_architecture/pull/67) +* update the casbin to asynchronous by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#69](https://github.com/fastapi-practices/fastapi_best_architecture/pull/69) +* Update tests structure. by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#68](https://github.com/fastapi-practices/fastapi_best_architecture/pull/68) +* Add apis rate limiter by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#72](https://github.com/fastapi-practices/fastapi_best_architecture/pull/72) +* add tests exclusion E402 rule by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#73](https://github.com/fastapi-practices/fastapi_best_architecture/pull/73) +* update where query by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#74](https://github.com/fastapi-practices/fastapi_best_architecture/pull/74) +* simplify crud method naming by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#75](https://github.com/fastapi-practices/fastapi_best_architecture/pull/75) +* add login logs by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#76](https://github.com/fastapi-practices/fastapi_best_architecture/pull/76) +* add different log files by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#77](https://github.com/fastapi-practices/fastapi_best_architecture/pull/77) +* add offline ip location resolution by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#78](https://github.com/fastapi-practices/fastapi_best_architecture/pull/78) +* add api module Interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#79](https://github.com/fastapi-practices/fastapi_best_architecture/pull/79) +* update token handling logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#83](https://github.com/fastapi-practices/fastapi_best_architecture/pull/83) +* add jwt authentication middleware by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#84](https://github.com/fastapi-practices/fastapi_best_architecture/pull/84) +* Fix background task not executed by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#86](https://github.com/fastapi-practices/fastapi_best_architecture/pull/86) +* Fix the merge issues by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#87](https://github.com/fastapi-practices/fastapi_best_architecture/pull/87) +* Update docker one-click deployment by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#88](https://github.com/fastapi-practices/fastapi_best_architecture/pull/88) +* Add role-related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#89](https://github.com/fastapi-practices/fastapi_best_architecture/pull/89) +* Bump cryptography from 39.0.1 to 41.0.0 by [@dependabot](https://github.com/dependabot) in [fastapi-practices/fastapi_best_architecture#90](https://github.com/fastapi-practices/fastapi_best_architecture/pull/90) +* Add assertion error handler. by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#93](https://github.com/fastapi-practices/fastapi_best_architecture/pull/93) +* Add operation log related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#92](https://github.com/fastapi-practices/fastapi_best_architecture/pull/92) +* Fix user authorization lock by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#94](https://github.com/fastapi-practices/fastapi_best_architecture/pull/94) +* Fix the opera log cost_time parameter by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#95](https://github.com/fastapi-practices/fastapi_best_architecture/pull/95) +* Add os and browser parameters to opera log by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#97](https://github.com/fastapi-practices/fastapi_best_architecture/pull/97) +* Uniform schema class naming convention style. by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#98](https://github.com/fastapi-practices/fastapi_best_architecture/pull/98) +* Add sync to async decorator support by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#96](https://github.com/fastapi-practices/fastapi_best_architecture/pull/96) +* Add department-related interfaces and others by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#101](https://github.com/fastapi-practices/fastapi_best_architecture/pull/101) +* Remove useless jwt role_ids by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#103](https://github.com/fastapi-practices/fastapi_best_architecture/pull/103) +* Add departmental status authentication by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#104](https://github.com/fastapi-practices/fastapi_best_architecture/pull/104) +* Add casbine-related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#107](https://github.com/fastapi-practices/fastapi_best_architecture/pull/107) +* Replace aioredis to redis. by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#108](https://github.com/fastapi-practices/fastapi_best_architecture/pull/108) +* opera_log_middleware method split by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#105](https://github.com/fastapi-practices/fastapi_best_architecture/pull/105) +* Fix offline parse ip info by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#112](https://github.com/fastapi-practices/fastapi_best_architecture/pull/112) +* Update the README document by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#113](https://github.com/fastapi-practices/fastapi_best_architecture/pull/113) +* Update development process suggestions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#114](https://github.com/fastapi-practices/fastapi_best_architecture/pull/114) +* Fix log table msg field length by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#117](https://github.com/fastapi-practices/fastapi_best_architecture/pull/117) +* Add menu-related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#118](https://github.com/fastapi-practices/fastapi_best_architecture/pull/118) +* Omitting table names from the autogenerate process by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#125](https://github.com/fastapi-practices/fastapi_best_architecture/pull/125) +* Add login graphic captcha by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#124](https://github.com/fastapi-practices/fastapi_best_architecture/pull/124) +* fix the operation log storage exception by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#130](https://github.com/fastapi-practices/fastapi_best_architecture/pull/130) +* add dictionary management interface by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#127](https://github.com/fastapi-practices/fastapi_best_architecture/pull/127) +* Update and fix permissions logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#129](https://github.com/fastapi-practices/fastapi_best_architecture/pull/129) +* Update JWT status detection by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#133](https://github.com/fastapi-practices/fastapi_best_architecture/pull/133) +* The level field is deprecated but remained by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#134](https://github.com/fastapi-practices/fastapi_best_architecture/pull/134) +* Add system monitoring interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#135](https://github.com/fastapi-practices/fastapi_best_architecture/pull/135) +* Fix the operation log message error by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#140](https://github.com/fastapi-practices/fastapi_best_architecture/pull/140) +* Update the server monitoring interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#141](https://github.com/fastapi-practices/fastapi_best_architecture/pull/141) +* Update the status field type to int by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#143](https://github.com/fastapi-practices/fastapi_best_architecture/pull/143) +* Fix the operation log field type error by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#145](https://github.com/fastapi-practices/fastapi_best_architecture/pull/145) +* Fix the exception handler HTTPException type error by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#146](https://github.com/fastapi-practices/fastapi_best_architecture/pull/146) +* Add the schema base class by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#148](https://github.com/fastapi-practices/fastapi_best_architecture/pull/148) +* Add datetime util by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#149](https://github.com/fastapi-practices/fastapi_best_architecture/pull/149) +* Fix permitted exception. by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#151](https://github.com/fastapi-practices/fastapi_best_architecture/pull/151) +* Refactor global datetime to timezone datetime by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#152](https://github.com/fastapi-practices/fastapi_best_architecture/pull/152) +* Add processing after password reset by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#154](https://github.com/fastapi-practices/fastapi_best_architecture/pull/154) +* Update some routing groups by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#155](https://github.com/fastapi-practices/fastapi_best_architecture/pull/155) +* Add task-related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#157](https://github.com/fastapi-practices/fastapi_best_architecture/pull/157) +* Update the instructions in the readme by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#159](https://github.com/fastapi-practices/fastapi_best_architecture/pull/159) +* Update some interface permission checks by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#158](https://github.com/fastapi-practices/fastapi_best_architecture/pull/158) +* Add database init sql files by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#160](https://github.com/fastapi-practices/fastapi_best_architecture/pull/160) +* Adapt to frontend by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#162](https://github.com/fastapi-practices/fastapi_best_architecture/pull/162) +* Update menu handling logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#163](https://github.com/fastapi-practices/fastapi_best_architecture/pull/163) +* Bump fastapi from 0.95.2 to 0.99.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#164](https://github.com/fastapi-practices/fastapi_best_architecture/pull/164) +* Enable login interface captcha function by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#165](https://github.com/fastapi-practices/fastapi_best_architecture/pull/165) +* Fix CORS 500 status code exception by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#167](https://github.com/fastapi-practices/fastapi_best_architecture/pull/167) +* Add menu table title field by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#170](https://github.com/fastapi-practices/fastapi_best_architecture/pull/170) +* fix tree data algorithms exception by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#169](https://github.com/fastapi-practices/fastapi_best_architecture/pull/169) +* Fix the menu query children exception by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#171](https://github.com/fastapi-practices/fastapi_best_architecture/pull/171) +* Custom request rate limit callback function by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#174](https://github.com/fastapi-practices/fastapi_best_architecture/pull/174) +* Add demo site mode by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#173](https://github.com/fastapi-practices/fastapi_best_architecture/pull/173) +* Add query users by department ID by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#175](https://github.com/fastapi-practices/fastapi_best_architecture/pull/175) +* Update monitoring return data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#176](https://github.com/fastapi-practices/fastapi_best_architecture/pull/176) +* Update user role interface to standalone by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#177](https://github.com/fastapi-practices/fastapi_best_architecture/pull/177) +* Add get roles related interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#178](https://github.com/fastapi-practices/fastapi_best_architecture/pull/178) +* Add the role status conditional query by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#181](https://github.com/fastapi-practices/fastapi_best_architecture/pull/181) +* Update role menu interface is standalone by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#182](https://github.com/fastapi-practices/fastapi_best_architecture/pull/182) +* Add interface to get all menus of a role by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#183](https://github.com/fastapi-practices/fastapi_best_architecture/pull/183) +* Fix schema enum condition exception by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#185](https://github.com/fastapi-practices/fastapi_best_architecture/pull/185) +* Bump Async SQLAlchemy Adapter from 1.1.0 to 1.2.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#187](https://github.com/fastapi-practices/fastapi_best_architecture/pull/187) +* Bump cryptography from 41.0.0 to 41.0.2 by [@dependabot](https://github.com/dependabot) in [fastapi-practices/fastapi_best_architecture#179](https://github.com/fastapi-practices/fastapi_best_architecture/pull/179) +* Update SQL files and use them as execution targets by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#188](https://github.com/fastapi-practices/fastapi_best_architecture/pull/188) +* Add user password encryption salt by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#191](https://github.com/fastapi-practices/fastapi_best_architecture/pull/191) +* Update roles and nickname fields to be optiona by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#190](https://github.com/fastapi-practices/fastapi_best_architecture/pull/190) +* Fix casbin async enforcer by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#192](https://github.com/fastapi-practices/fastapi_best_architecture/pull/192) +* Add more Casbin related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#195](https://github.com/fastapi-practices/fastapi_best_architecture/pull/195) +* Update the nickname field creation logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#196](https://github.com/fastapi-practices/fastapi_best_architecture/pull/196) +* Update the Casbin model matcher rules by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#197](https://github.com/fastapi-practices/fastapi_best_architecture/pull/197) +* Add api and casbin related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#198](https://github.com/fastapi-practices/fastapi_best_architecture/pull/198) +* Update asynccasbin to casbin async api by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#199](https://github.com/fastapi-practices/fastapi_best_architecture/pull/199) +* Fix the interface logic for dept details by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#201](https://github.com/fastapi-practices/fastapi_best_architecture/pull/201) +* Add ItsDangerous request parameters encryption by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#203](https://github.com/fastapi-practices/fastapi_best_architecture/pull/203) +* Add jwt login whitelist by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#204](https://github.com/fastapi-practices/fastapi_best_architecture/pull/204) +* Add ip location cache by [@downdawn](https://github.com/downdawn) in [fastapi-practices/fastapi_best_architecture#205](https://github.com/fastapi-practices/fastapi_best_architecture/pull/205) +* Fix int enum class inheritance by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#208](https://github.com/fastapi-practices/fastapi_best_architecture/pull/208) +* Fix the task interface return data by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#215](https://github.com/fastapi-practices/fastapi_best_architecture/pull/215) +* Update the README document by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#214](https://github.com/fastapi-practices/fastapi_best_architecture/pull/214) +* Fix token whitelist and new token storage by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#220](https://github.com/fastapi-practices/fastapi_best_architecture/pull/220) +* Optimize role menu authorization logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#221](https://github.com/fastapi-practices/fastapi_best_architecture/pull/221) +* Simplified query interface returns data serialization by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#219](https://github.com/fastapi-practices/fastapi_best_architecture/pull/219) +* Update the global unified response code by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#223](https://github.com/fastapi-practices/fastapi_best_architecture/pull/223) +* Fix global unknown exception return by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#224](https://github.com/fastapi-practices/fastapi_best_architecture/pull/224) +* Update the pytz library to zoneinfo by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#226](https://github.com/fastapi-practices/fastapi_best_architecture/pull/226) +* Add token decoding expiration exception by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#227](https://github.com/fastapi-practices/fastapi_best_architecture/pull/227) +* Fix the task run method and data type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#228](https://github.com/fastapi-practices/fastapi_best_architecture/pull/228) +* Remove the NoReturn return type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#232](https://github.com/fastapi-practices/fastapi_best_architecture/pull/232) +* Add init pytest data sql file by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#231](https://github.com/fastapi-practices/fastapi_best_architecture/pull/231) +* Fix pytest interface unit tests by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#233](https://github.com/fastapi-practices/fastapi_best_architecture/pull/233) +* Replace APScheduler to Celery asynchronous tasks by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#229](https://github.com/fastapi-practices/fastapi_best_architecture/pull/229) +* Fix the conflict between Access and OperaLog middleware by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#236](https://github.com/fastapi-practices/fastapi_best_architecture/pull/236) +* Fix unregistered error received when celery call task by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#239](https://github.com/fastapi-practices/fastapi_best_architecture/pull/239) +* Fix database engine UUID type compatibility by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#241](https://github.com/fastapi-practices/fastapi_best_architecture/pull/241) +* adopt ruff formatter by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#242](https://github.com/fastapi-practices/fastapi_best_architecture/pull/242) +* Bump cryptography from 41.0.2 to 41.0.6 by [@dependabot](https://github.com/dependabot) in [fastapi-practices/fastapi_best_architecture#243](https://github.com/fastapi-practices/fastapi_best_architecture/pull/243) +* Add a telegram interactive link by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#245](https://github.com/fastapi-practices/fastapi_best_architecture/pull/245) +* Fix validation error log code return type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#247](https://github.com/fastapi-practices/fastapi_best_architecture/pull/247) +* Fix refresh token interface user type format by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#248](https://github.com/fastapi-practices/fastapi_best_architecture/pull/248) +* Optimize operation log code type logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#249](https://github.com/fastapi-practices/fastapi_best_architecture/pull/249) +* Fix get all G rules interface logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#250](https://github.com/fastapi-practices/fastapi_best_architecture/pull/250) +* Simplify the multivariate expression of exceptions handler by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#252](https://github.com/fastapi-practices/fastapi_best_architecture/pull/252) +* Fix exception handler parameter call by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#253](https://github.com/fastapi-practices/fastapi_best_architecture/pull/253) +* Prepare to lock the pydantic-v1 branch by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#254](https://github.com/fastapi-practices/fastapi_best_architecture/pull/254) +* Add a stand-alone assertion error handler by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#255](https://github.com/fastapi-practices/fastapi_best_architecture/pull/255) +* Clean up todo and fix typo by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#256](https://github.com/fastapi-practices/fastapi_best_architecture/pull/256) +* Migrate to pydantic-v2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#246](https://github.com/fastapi-practices/fastapi_best_architecture/pull/246) +* Add pydantic-v2 migration reminder by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#257](https://github.com/fastapi-practices/fastapi_best_architecture/pull/257) +* Add the project status page to the README by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#259](https://github.com/fastapi-practices/fastapi_best_architecture/pull/259) +* Clean up outdated pydantic dict methods by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#262](https://github.com/fastapi-practices/fastapi_best_architecture/pull/262) +* Fix use request.form() in middleware by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#260](https://github.com/fastapi-practices/fastapi_best_architecture/pull/260) +* Reconstruct RBAC authentication logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#264](https://github.com/fastapi-practices/fastapi_best_architecture/pull/264) +* Attempt to optimize serialization performance by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#266](https://github.com/fastapi-practices/fastapi_best_architecture/pull/266) +* Update schemas naming style by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#272](https://github.com/fastapi-practices/fastapi_best_architecture/pull/272) +* Update sponsor links and FUNDING by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#273](https://github.com/fastapi-practices/fastapi_best_architecture/pull/273) +* Fix dept and menu parent id update logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#274](https://github.com/fastapi-practices/fastapi_best_architecture/pull/274) +* Update interface coding style by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#275](https://github.com/fastapi-practices/fastapi_best_architecture/pull/275) +* Update dao and service instantiation styles by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#276](https://github.com/fastapi-practices/fastapi_best_architecture/pull/276) +* Add custom email string type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#277](https://github.com/fastapi-practices/fastapi_best_architecture/pull/277) +* Fix custom validator exception serialization in dev mode by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#278](https://github.com/fastapi-practices/fastapi_best_architecture/pull/278) +* Restore the Github ci workflows by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#281](https://github.com/fastapi-practices/fastapi_best_architecture/pull/281) +* Add the pdm project manager by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#282](https://github.com/fastapi-practices/fastapi_best_architecture/pull/282) +* Add the front-end docker-compose script by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#283](https://github.com/fastapi-practices/fastapi_best_architecture/pull/283) +* Update the response status code in exception handlers by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#292](https://github.com/fastapi-practices/fastapi_best_architecture/pull/292) +* Update interface file directory level by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#295](https://github.com/fastapi-practices/fastapi_best_architecture/pull/295) +* Add the repository star map by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#296](https://github.com/fastapi-practices/fastapi_best_architecture/pull/296) +* Add OAuth 2.0 authorization login by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#293](https://github.com/fastapi-practices/fastapi_best_architecture/pull/293) +* Prepare to lock the legacy branch by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#301](https://github.com/fastapi-practices/fastapi_best_architecture/pull/301) +* Update the README.md branch prompt by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#302](https://github.com/fastapi-practices/fastapi_best_architecture/pull/302) +* Refactor the backend architecture by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#299](https://github.com/fastapi-practices/fastapi_best_architecture/pull/299) +* Fix English README.md update date by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#308](https://github.com/fastapi-practices/fastapi_best_architecture/pull/308) +* Add backend scripts description by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#309](https://github.com/fastapi-practices/fastapi_best_architecture/pull/309) +* Fix missing data from alembic migration by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#312](https://github.com/fastapi-practices/fastapi_best_architecture/pull/312) +* Update CRUDBase to sqlalchemy-crud-plus by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#317](https://github.com/fastapi-practices/fastapi_best_architecture/pull/317) +* Upgrade and update fastapi service startup by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#319](https://github.com/fastapi-practices/fastapi_best_architecture/pull/319) +* Delete the gzip middleware to improve performance by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#325](https://github.com/fastapi-practices/fastapi_best_architecture/pull/325) +* Add interface fast response method by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#327](https://github.com/fastapi-practices/fastapi_best_architecture/pull/327) +* Update the opera log middleware task by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#326](https://github.com/fastapi-practices/fastapi_best_architecture/pull/326) +* Add test account to README by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#330](https://github.com/fastapi-practices/fastapi_best_architecture/pull/330) +* Restore the main startup of fastapi service by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#336](https://github.com/fastapi-practices/fastapi_best_architecture/pull/336) +* Update app route definition rules by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#341](https://github.com/fastapi-practices/fastapi_best_architecture/pull/341) +* Add Linux Do OAuth2 login by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#343](https://github.com/fastapi-practices/fastapi_best_architecture/pull/343) +* Fix user social binding query by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#344](https://github.com/fastapi-practices/fastapi_best_architecture/pull/344) +* Refactor global log default handler by [@obrua](https://github.com/obrua) in [fastapi-practices/fastapi_best_architecture#347](https://github.com/fastapi-practices/fastapi_best_architecture/pull/347) +* Add code generator app by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#318](https://github.com/fastapi-practices/fastapi_best_architecture/pull/318) +* Update and enable access log middleware by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#348](https://github.com/fastapi-practices/fastapi_best_architecture/pull/348) +* Add code generator README document by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#349](https://github.com/fastapi-practices/fastapi_best_architecture/pull/349) +* Fix model template conditional syntax by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#351](https://github.com/fastapi-practices/fastapi_best_architecture/pull/351) +* Update code generation model column type storage by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#352](https://github.com/fastapi-practices/fastapi_best_architecture/pull/352) +* Fix gen model and schema template formatting by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#356](https://github.com/fastapi-practices/fastapi_best_architecture/pull/356) +* Add code generator to create init files by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#358](https://github.com/fastapi-practices/fastapi_best_architecture/pull/358) +* Fix alembic migration failure caused by model by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#359](https://github.com/fastapi-practices/fastapi_best_architecture/pull/359) +* Update the docker-compose deployment script by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#360](https://github.com/fastapi-practices/fastapi_best_architecture/pull/360) +* Update oauth2 route naming and return by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#361](https://github.com/fastapi-practices/fastapi_best_architecture/pull/361) +* Bump fast captcha version to 0.3.2 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#362](https://github.com/fastapi-practices/fastapi_best_architecture/pull/362) +* Update crud user staff field logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#363](https://github.com/fastapi-practices/fastapi_best_architecture/pull/363) +* Fix code auto-generated model creation by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#364](https://github.com/fastapi-practices/fastapi_best_architecture/pull/364) +* Add page to display dynamic configuration by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#365](https://github.com/fastapi-practices/fastapi_best_architecture/pull/365) +* Fix celery asynchronous task execution by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#367](https://github.com/fastapi-practices/fastapi_best_architecture/pull/367) +* Update operation log middleware info reading by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#368](https://github.com/fastapi-practices/fastapi_best_architecture/pull/368) +* Update create new token function return type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#369](https://github.com/fastapi-practices/fastapi_best_architecture/pull/369) +* Update access log cost time style by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#370](https://github.com/fastapi-practices/fastapi_best_architecture/pull/370) +* Update code generate business model time column by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#371](https://github.com/fastapi-practices/fastapi_best_architecture/pull/371) +* Add custom code template pathname config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#372](https://github.com/fastapi-practices/fastapi_best_architecture/pull/372) +* Update some code generation api and params by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#373](https://github.com/fastapi-practices/fastapi_best_architecture/pull/373) +* Update code generate download api auth by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#376](https://github.com/fastapi-practices/fastapi_best_architecture/pull/376) +* Update current menu status auth by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#374](https://github.com/fastapi-practices/fastapi_best_architecture/pull/374) +* Fix code generation model create and update by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#378](https://github.com/fastapi-practices/fastapi_best_architecture/pull/378) +* Update user and auth error message by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#379](https://github.com/fastapi-practices/fastapi_best_architecture/pull/379) +* Add directory tree and update app notes by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#380](https://github.com/fastapi-practices/fastapi_best_architecture/pull/380) +* Optimize serialization and jwt performance by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#382](https://github.com/fastapi-practices/fastapi_best_architecture/pull/382) +* Fix arm system cpu frequency retrieval by [@yshan2028](https://github.com/yshan2028) in [fastapi-practices/fastapi_best_architecture#385](https://github.com/fastapi-practices/fastapi_best_architecture/pull/385) +* Fix logging when a login error occurs by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#386](https://github.com/fastapi-practices/fastapi_best_architecture/pull/386) +* Update redis cache prefix separator to `:` by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#387](https://github.com/fastapi-practices/fastapi_best_architecture/pull/387) +* Bump sqlalchemy crud plus version to 1.3.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#388](https://github.com/fastapi-practices/fastapi_best_architecture/pull/388) +* Update the README announcement to note by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#390](https://github.com/fastapi-practices/fastapi_best_architecture/pull/390) +* Fix code generation to new features by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#393](https://github.com/fastapi-practices/fastapi_best_architecture/pull/393) +* Fix OAuth2 user query conditions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#396](https://github.com/fastapi-practices/fastapi_best_architecture/pull/396) +* Fix the user permissions update services by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#397](https://github.com/fastapi-practices/fastapi_best_architecture/pull/397) +* Update code generate comment column format by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#399](https://github.com/fastapi-practices/fastapi_best_architecture/pull/399) +* Update the interactive link address by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#402](https://github.com/fastapi-practices/fastapi_best_architecture/pull/402) +* Updated refresh token storage logic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#403](https://github.com/fastapi-practices/fastapi_best_architecture/pull/403) +* Fix server monitor io blocking by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#404](https://github.com/fastapi-practices/fastapi_best_architecture/pull/404) +* Fix cookie expiration time zone by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#408](https://github.com/fastapi-practices/fastapi_best_architecture/pull/408) +* Add request trace ID record by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#409](https://github.com/fastapi-practices/fastapi_best_architecture/pull/409) +* Optimize the naming of setting params by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#410](https://github.com/fastapi-practices/fastapi_best_architecture/pull/410) +* Add trace ID to exception handlers by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#411](https://github.com/fastapi-practices/fastapi_best_architecture/pull/411) +* Update the global exception log stack by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#406](https://github.com/fastapi-practices/fastapi_best_architecture/pull/406) +* Bump pydantic from 2.8.1 to 2.9.1 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#412](https://github.com/fastapi-practices/fastapi_best_architecture/pull/412) +* Optimize exception info opera log record by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#413](https://github.com/fastapi-practices/fastapi_best_architecture/pull/413) +* Fix log output and logging levels by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#414](https://github.com/fastapi-practices/fastapi_best_architecture/pull/414) +* Fix exception logging in opera log by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#417](https://github.com/fastapi-practices/fastapi_best_architecture/pull/417) +* Fix the gen model template formatting by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#416](https://github.com/fastapi-practices/fastapi_best_architecture/pull/416) +* Optimize the internal implementation of serializers by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#419](https://github.com/fastapi-practices/fastapi_best_architecture/pull/419) +* Fix for create new token cache delete by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#420](https://github.com/fastapi-practices/fastapi_best_architecture/pull/420) +* Update JWT errors class import by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#421](https://github.com/fastapi-practices/fastapi_best_architecture/pull/421) +* Update multi login sync update refresh tokens by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#422](https://github.com/fastapi-practices/fastapi_best_architecture/pull/422) +* Update sync function calls in JWT by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#423](https://github.com/fastapi-practices/fastapi_best_architecture/pull/423) +* Fix the missing OAuth2 interface parameters by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#425](https://github.com/fastapi-practices/fastapi_best_architecture/pull/425) +* Add request state middleware by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#426](https://github.com/fastapi-practices/fastapi_best_architecture/pull/426) +* Fix pydantic field and model validator by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#427](https://github.com/fastapi-practices/fastapi_best_architecture/pull/427) +* Fix the OAuth2 service login log task by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#428](https://github.com/fastapi-practices/fastapi_best_architecture/pull/428) +* Update official documentation link to README by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#429](https://github.com/fastapi-practices/fastapi_best_architecture/pull/429) +* Optimize and normalize the code generator by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#430](https://github.com/fastapi-practices/fastapi_best_architecture/pull/430) +* Bump redis from 5.0.1 to 5.1.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#433](https://github.com/fastapi-practices/fastapi_best_architecture/pull/433) +* Update interactive link descriptions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#434](https://github.com/fastapi-practices/fastapi_best_architecture/pull/434) +* Optimize the serialize return of SQLA select by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#436](https://github.com/fastapi-practices/fastapi_best_architecture/pull/436) +* Update project manager pdm to uv by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#440](https://github.com/fastapi-practices/fastapi_best_architecture/pull/440) +* Add asynchronous socketio application server by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#437](https://github.com/fastapi-practices/fastapi_best_architecture/pull/437) +* Add dependency-groups by PEP 735 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#444](https://github.com/fastapi-practices/fastapi_best_architecture/pull/444) +* Update the usage documentation in README by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#449](https://github.com/fastapi-practices/fastapi_best_architecture/pull/449) +* Bump sqlalchemy crud plus version to 1.5.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#450](https://github.com/fastapi-practices/fastapi_best_architecture/pull/450) +* Update singleton pattern class typing by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#452](https://github.com/fastapi-practices/fastapi_best_architecture/pull/452) +* Update system config to be dynamic by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#447](https://github.com/fastapi-practices/fastapi_best_architecture/pull/447) +* Update multiple version dependency specifiers by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#454](https://github.com/fastapi-practices/fastapi_best_architecture/pull/454) +* Fix typo in contribution description by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#456](https://github.com/fastapi-practices/fastapi_best_architecture/pull/456) +* Fix code generation file missing by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#457](https://github.com/fastapi-practices/fastapi_best_architecture/pull/457) +* Update the celery configuration and tasks by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#458](https://github.com/fastapi-practices/fastapi_best_architecture/pull/458) +* Update some service class invocations by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#459](https://github.com/fastapi-practices/fastapi_best_architecture/pull/459) +* Update code generator API file structure by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#460](https://github.com/fastapi-practices/fastapi_best_architecture/pull/460) +* Update api body params to schema by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#461](https://github.com/fastapi-practices/fastapi_best_architecture/pull/461) +* Fix celery service functions error by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#462](https://github.com/fastapi-practices/fastapi_best_architecture/pull/462) +* Update user password encryption method by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#463](https://github.com/fastapi-practices/fastapi_best_architecture/pull/463) +* Update role-based data permissions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#465](https://github.com/fastapi-practices/fastapi_best_architecture/pull/465) +* Bump tornado from 6.4.1 to 6.4.2 in /backend by [@dependabot](https://github.com/dependabot) in [fastapi-practices/fastapi_best_architecture#466](https://github.com/fastapi-practices/fastapi_best_architecture/pull/466) +* Fix schema type of user role rule by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#467](https://github.com/fastapi-practices/fastapi_best_architecture/pull/467) +* Simplify data rule and remove type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#468](https://github.com/fastapi-practices/fastapi_best_architecture/pull/468) +* Add the project logo to README by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#469](https://github.com/fastapi-practices/fastapi_best_architecture/pull/469) +* Optimized user auth for auth service by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#472](https://github.com/fastapi-practices/fastapi_best_architecture/pull/472) +* Fix data rule expression column comment by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#473](https://github.com/fastapi-practices/fastapi_best_architecture/pull/473) +* Fix and update alembic env and ini by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#474](https://github.com/fastapi-practices/fastapi_best_architecture/pull/474) +* Fix login log parameter error in task by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#476](https://github.com/fastapi-practices/fastapi_best_architecture/pull/476) +* Remove data scope in the role model by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#478](https://github.com/fastapi-practices/fastapi_best_architecture/pull/478) +* Add postgresql database support by [@Meepoljdx](https://github.com/Meepoljdx) in [fastapi-practices/fastapi_best_architecture#475](https://github.com/fastapi-practices/fastapi_best_architecture/pull/475) +* Update opera log cost time precision by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#479](https://github.com/fastapi-practices/fastapi_best_architecture/pull/479) +* Update opera middleware request args parse by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#481](https://github.com/fastapi-practices/fastapi_best_architecture/pull/481) +* Bump msgspec from 0.18.6 to 0.19.0 by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#482](https://github.com/fastapi-practices/fastapi_best_architecture/pull/482) +* Fix user cache when updated user role by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#483](https://github.com/fastapi-practices/fastapi_best_architecture/pull/483) +* Update the route version define location by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#485](https://github.com/fastapi-practices/fastapi_best_architecture/pull/485) +* Optimize docker deploy settings and scripts by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#486](https://github.com/fastapi-practices/fastapi_best_architecture/pull/486) +* Add system notice interface by [@dividduang](https://github.com/dividduang) in [fastapi-practices/fastapi_best_architecture#487](https://github.com/fastapi-practices/fastapi_best_architecture/pull/487) +* Add response model include data schema by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#490](https://github.com/fastapi-practices/fastapi_best_architecture/pull/490) +* Update redocs arg and url to redoc by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#493](https://github.com/fastapi-practices/fastapi_best_architecture/pull/493) +* Fix serialization when pagination is empty by [@qhp13654398483](https://github.com/qhp13654398483) in [fastapi-practices/fastapi_best_architecture#491](https://github.com/fastapi-practices/fastapi_best_architecture/pull/491) +* Update return schema of query interface by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#492](https://github.com/fastapi-practices/fastapi_best_architecture/pull/492) +* Add token related interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#495](https://github.com/fastapi-practices/fastapi_best_architecture/pull/495) +* Fix return schema of user me api by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#497](https://github.com/fastapi-practices/fastapi_best_architecture/pull/497) +* Fix current user info detail schema by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#499](https://github.com/fastapi-practices/fastapi_best_architecture/pull/499) +* Update menu field show to display by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#498](https://github.com/fastapi-practices/fastapi_best_architecture/pull/498) +* Fix casbin policy api return schema by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#500](https://github.com/fastapi-practices/fastapi_best_architecture/pull/500) +* Fix opera log of non-dict request body by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#501](https://github.com/fastapi-practices/fastapi_best_architecture/pull/501) +* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#504](https://github.com/fastapi-practices/fastapi_best_architecture/pull/504) +* Fix the return datetime data encoder by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#505](https://github.com/fastapi-practices/fastapi_best_architecture/pull/505) +* Fix fastapi config variable naming and type by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#506](https://github.com/fastapi-practices/fastapi_best_architecture/pull/506) +* Fix the user pagination api return schema by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#507](https://github.com/fastapi-practices/fastapi_best_architecture/pull/507) +* Fix OAuth2 service register user args by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#508](https://github.com/fastapi-practices/fastapi_best_architecture/pull/508) +* Fix OAuth2 service user last login time by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#509](https://github.com/fastapi-practices/fastapi_best_architecture/pull/509) +* Fix OAuth2 service user last login time by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#510](https://github.com/fastapi-practices/fastapi_best_architecture/pull/510) +* Add plugin system and notice plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#503](https://github.com/fastapi-practices/fastapi_best_architecture/pull/503) +* Delete the threads in gunicorn config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#512](https://github.com/fastapi-practices/fastapi_best_architecture/pull/512) +* Add plugin requirements auto install functions by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#514](https://github.com/fastapi-practices/fastapi_best_architecture/pull/514) +* Update casbin rbac verify to plugin by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#513](https://github.com/fastapi-practices/fastapi_best_architecture/pull/513) +* Update the logic for create new token by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#516](https://github.com/fastapi-practices/fastapi_best_architecture/pull/516) +* Add local file upload interfaces by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#489](https://github.com/fastapi-practices/fastapi_best_architecture/pull/489) +* Update loguru and deploy log config by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#517](https://github.com/fastapi-practices/fastapi_best_architecture/pull/517) +* Delete the model redundancy level field by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#518](https://github.com/fastapi-practices/fastapi_best_architecture/pull/518) +* Update the built-in features in README by [@wu-clan](https://github.com/wu-clan) in [fastapi-practices/fastapi_best_architecture#519](https://github.com/fastapi-practices/fastapi_best_architecture/pull/519) ## New Contributors -* [@downdawn](https://github.com/downdawn) made their first contribution in [#15](https://github.com/fastapi-practices/fastapi_best_architecture/pull/15) -* [@dependabot](https://github.com/dependabot) made their first contribution in [#48](https://github.com/fastapi-practices/fastapi_best_architecture/pull/48) -* [@obrua](https://github.com/obrua) made their first contribution in [#347](https://github.com/fastapi-practices/fastapi_best_architecture/pull/347) -* [@yshan2028](https://github.com/yshan2028) made their first contribution in [#385](https://github.com/fastapi-practices/fastapi_best_architecture/pull/385) -* [@Meepoljdx](https://github.com/Meepoljdx) made their first contribution in [#475](https://github.com/fastapi-practices/fastapi_best_architecture/pull/475) -* [@dividduang](https://github.com/dividduang) made their first contribution in [#487](https://github.com/fastapi-practices/fastapi_best_architecture/pull/487) -* [@qhp13654398483](https://github.com/qhp13654398483) made their first contribution in [#491](https://github.com/fastapi-practices/fastapi_best_architecture/pull/491) +* [@downdawn](https://github.com/downdawn) made their first contribution in [fastapi-practices/fastapi_best_architecture#15](https://github.com/fastapi-practices/fastapi_best_architecture/pull/15) +* [@dependabot](https://github.com/dependabot) made their first contribution in [fastapi-practices/fastapi_best_architecture#48](https://github.com/fastapi-practices/fastapi_best_architecture/pull/48) +* [@obrua](https://github.com/obrua) made their first contribution in [fastapi-practices/fastapi_best_architecture#347](https://github.com/fastapi-practices/fastapi_best_architecture/pull/347) +* [@yshan2028](https://github.com/yshan2028) made their first contribution in [fastapi-practices/fastapi_best_architecture#385](https://github.com/fastapi-practices/fastapi_best_architecture/pull/385) +* [@Meepoljdx](https://github.com/Meepoljdx) made their first contribution in [fastapi-practices/fastapi_best_architecture#475](https://github.com/fastapi-practices/fastapi_best_architecture/pull/475) +* [@dividduang](https://github.com/dividduang) made their first contribution in [fastapi-practices/fastapi_best_architecture#487](https://github.com/fastapi-practices/fastapi_best_architecture/pull/487) +* [@qhp13654398483](https://github.com/qhp13654398483) made their first contribution in [fastapi-practices/fastapi_best_architecture#491](https://github.com/fastapi-practices/fastapi_best_architecture/pull/491) **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/commits/v1.0.0 @@ -1223,39 +1407,44 @@ [Changes][v1.0.0] -[v1.12.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.2...v1.12.0 -[v1.11.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.1...v1.11.2 -[v1.11.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.11.0...v1.11.1 -[v1.11.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.4...v1.11.0 -[v1.10.4]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.3...v1.10.4 -[v1.10.3]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.2...v1.10.3 -[v1.10.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.1...v1.10.2 -[v1.10.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.0...v1.10.1 -[v1.10.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.9.0...v1.10.0 -[v1.9.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.3...v1.9.0 -[v1.8.3]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.2...v1.8.3 -[v1.8.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.1...v1.8.2 -[v1.8.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.0...v1.8.1 -[v1.8.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.7.0...v1.8.0 -[v1.7.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.6.0...v1.7.0 -[v1.6.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.2...v1.6.0 -[v1.5.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.1...v1.5.2 -[v1.5.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.0...v1.5.1 -[v1.5.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.3...v1.5.0 -[v1.4.3]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.2...v1.4.3 -[v1.4.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.1...v1.4.2 -[v1.4.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.0...v1.4.1 -[v1.4.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.3.0...v1.4.0 -[v1.3.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.2.0...v1.3.0 -[v1.2.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.2...v1.2.0 -[v1.1.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.1...v1.1.2 -[v1.1.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.0...v1.1.1 -[v1.1.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.5...v1.1.0 -[v1.0.5]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.4...v1.0.5 -[v1.0.4]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.3...v1.0.4 -[v1.0.3]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.2...v1.0.3 -[v1.0.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.1...v1.0.2 -[v1.0.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.0...v1.0.1 -[v1.0.0]: https://github.com/fastapi-practices/fastapi_best_architecture/tree/v1.0.0 +[v1.13.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.13.0...v1.13.1 +[v1.13.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.12.3...v1.13.0 +[v1.12.3]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.12.2...v1.12.3 +[v1.12.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.12.1...v1.12.2 +[v1.12.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.12.0...v1.12.1 +[v1.12.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.11.2...v1.12.0 +[v1.11.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.11.1...v1.11.2 +[v1.11.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.11.0...v1.11.1 +[v1.11.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.10.4...v1.11.0 +[v1.10.4]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.10.3...v1.10.4 +[v1.10.3]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.10.2...v1.10.3 +[v1.10.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.10.1...v1.10.2 +[v1.10.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.10.0...v1.10.1 +[v1.10.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.9.0...v1.10.0 +[v1.9.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.8.3...v1.9.0 +[v1.8.3]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.8.2...v1.8.3 +[v1.8.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.8.1...v1.8.2 +[v1.8.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.8.0...v1.8.1 +[v1.8.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.7.0...v1.8.0 +[v1.7.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.6.0...v1.7.0 +[v1.6.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.5.2...v1.6.0 +[v1.5.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.5.1...v1.5.2 +[v1.5.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.5.0...v1.5.1 +[v1.5.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.4.3...v1.5.0 +[v1.4.3]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.4.2...v1.4.3 +[v1.4.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.4.1...v1.4.2 +[v1.4.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.4.0...v1.4.1 +[v1.4.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.3.0...v1.4.0 +[v1.3.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.2.0...v1.3.0 +[v1.2.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.1.2...v1.2.0 +[v1.1.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.1.1...v1.1.2 +[v1.1.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.1.0...v1.1.1 +[v1.1.0]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.0.5...v1.1.0 +[v1.0.5]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.0.4...v1.0.5 +[v1.0.4]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.0.3...v1.0.4 +[v1.0.3]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.0.2...v1.0.3 +[v1.0.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.0.1...v1.0.2 +[v1.0.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.0.0...v1.0.1 +[v1.0.0]: https://github.com/fastapi-practices/fastapi-best-architecture/tree/v1.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a40fe1d..d9ee2a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ Go to the root directory of the project, open the terminal, and run the following command: ```sh - uv sync + uv run fba init --auto ``` 3. Checkout @@ -31,10 +31,8 @@ 4. Format and Lint - Auto-formatting and lint via `prek` - ```shell - prek run --all-files + fba format ``` 5. Commit and push @@ -55,7 +53,7 @@ - `migrate.sh`: Perform automatic database migration -- `scripts/format.sh`: Perform ruff format check +- `scripts/format.sh`: Perform ruff format with preview - `scripts/lint.sh`: Perform prek formatting diff --git a/Dockerfile b/Dockerfile index fb82f71..0d19a57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,3 @@ -# Select the image to build based on SERVER_TYPE, defaulting to fba_server, or docker-compose build args -ARG SERVER_TYPE=fba_server - # === Python environment from uv === FROM ghcr.io/astral-sh/uv:python3.10-bookworm-slim AS builder @@ -26,8 +23,8 @@ RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --locked --no-default-groups --group server --no-install-project -# === Runtime base server image === -FROM python:3.10-slim-bookworm AS base_server +# === Runtime server image === +FROM python:3.10-slim-bookworm RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources \ && apt-get update \ @@ -45,10 +42,6 @@ COPY --from=builder /fba /fba COPY --from=builder /usr/local /usr/local COPY deploy/backend/supervisor/supervisord.conf /etc/supervisor/supervisord.conf - -# === FastAPI server image === -FROM base_server AS fba_server - COPY deploy/backend/supervisor/fba_server.conf /etc/supervisor/conf.d/ RUN mkdir -p /var/log/fba @@ -56,6 +49,3 @@ RUN mkdir -p /var/log/fba EXPOSE 8001 CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"] - -# Build image -FROM ${SERVER_TYPE} diff --git a/SLIM_GUIDE.md b/SLIM_GUIDE.md new file mode 100644 index 0000000..bc24207 --- /dev/null +++ b/SLIM_GUIDE.md @@ -0,0 +1,272 @@ +# FBA-Slim 瘦身指南 + +> 本文档记录了 fba-slim 与 fba 完整版之间的差异,方便日后从完整版合并代码时快速瘦身 + +## 功能差异概览 + +| 模块 | 完整版 | 精简版 | 说明 | +|-------------------------------|-----|-----|-----------------------------| +| 用户认证 (JWT) | ✅ | ✅ | 完整保留 | +| RBAC (角色/菜单/部门/权限校验) | ✅ | ❌ | 整体移除,仅保留 `DependsSuperUser` | +| 用户 CRUD | ✅ | ✅ | 保留(移除 dept/role 关联) | +| 操作日志/登录日志 | ✅ | ❌ | 移除 DB 日志,中间件仅控制台输出 | +| 多级缓存 (Local + Redis + PubSub) | ✅ | ✅ | 完整保留 | +| Snowflake 分布式 ID | ✅ | ✅ | 完整保留 | +| 文件上传 | ✅ | ✅ | 完整保留 | +| 密码安全/历史记录 | ✅ | ✅ | 完整保留 | +| config 插件 | ✅ | ✅ | 保留 | +| 插件核心系统 | ✅ | ✅ | 完整保留 | +| Celery 任务系统 | ✅ | ❌ | 整体移除 | +| Socket.IO 实时通信 | ✅ | ❌ | 整体移除 | +| Prometheus + OTel 可观测性 | ✅ | ❌ | 整体移除 | +| 监控 API (online/redis/server) | ✅ | ❌ | 整体移除 | +| 数据权限 (DataRule/DataScope) | ✅ | ❌ | 整体移除 | +| dict 插件 | ✅ | ❌ | 整体移除 | +| email 插件 | ✅ | ❌ | 整体移除 | +| notice 插件 | ✅ | ❌ | 整体移除 | +| oauth2 插件 | ✅ | ❌ | 整体移除 | +| code_generator 插件 | ✅ | ❌ | 整体移除 | + +--- + +## 已删除的目录 + +``` +backend/app/task/ # Celery 任务系统 +backend/common/socketio/ # Socket.IO 实时通信 +backend/common/observability/ # Prometheus + OpenTelemetry +backend/common/prometheus/ # Prometheus 指标 +backend/app/admin/api/v1/monitor/ # 监控 API (online/redis/server) +backend/app/admin/tests/ # 测试文件 +backend/app/admin/api/v1/log/ # 日志 API (login_log/opera_log) +backend/plugin/dict/ # 字典插件 +backend/plugin/email/ # 邮件插件 +backend/plugin/notice/ # 通知插件 +backend/plugin/oauth2/ # OAuth2 插件 +backend/plugin/code_generator/ # 代码生成插件 +deploy/backend/grafana/ # Grafana 部署配置 +``` + +## 已删除的文件 + +``` +# RBAC (角色/菜单/部门) +backend/app/admin/model/role.py +backend/app/admin/model/menu.py +backend/app/admin/model/dept.py +backend/app/admin/model/m2m.py +backend/app/admin/schema/role.py +backend/app/admin/schema/menu.py +backend/app/admin/schema/dept.py +backend/app/admin/crud/crud_role.py +backend/app/admin/crud/crud_menu.py +backend/app/admin/crud/crud_dept.py +backend/app/admin/service/role_service.py +backend/app/admin/service/menu_service.py +backend/app/admin/service/dept_service.py +backend/app/admin/api/v1/sys/role.py +backend/app/admin/api/v1/sys/menu.py +backend/app/admin/api/v1/sys/dept.py +backend/common/security/rbac.py +backend/common/security/permission.py +backend/utils/build_tree.py + +# 数据权限 +backend/app/admin/model/data_rule.py +backend/app/admin/model/data_scope.py +backend/app/admin/schema/data_rule.py +backend/app/admin/schema/data_scope.py +backend/app/admin/schema/monitor.py +backend/app/admin/crud/crud_data_rule.py +backend/app/admin/crud/crud_data_scope.py +backend/app/admin/service/data_rule_service.py +backend/app/admin/service/data_scope_service.py +backend/app/admin/api/v1/sys/data_rule.py +backend/app/admin/api/v1/sys/data_scope.py + +# 可观测性 +backend/utils/otel.py + +# 日志系统 +backend/app/admin/model/login_log.py +backend/app/admin/model/opera_log.py +backend/app/admin/schema/login_log.py +backend/app/admin/schema/opera_log.py +backend/app/admin/crud/crud_login_log.py +backend/app/admin/crud/crud_opera_log.py +backend/app/admin/service/login_log_service.py +backend/app/admin/service/opera_log_service.py +backend/common/queue.py + +# 部署 +deploy/backend/supervisor/fba_celery_beat.conf +deploy/backend/supervisor/fba_celery_flower.conf +deploy/backend/supervisor/fba_celery_worker.conf +deploy/backend/grafana/dashboards/fba_celery.json +``` + +--- + +## 已修改的文件 + +### 模型层 + +| 文件 | 修改内容 | +|---------------------------------------|-------------------------------------| +| `backend/app/admin/model/__init__.py` | 仅保留 `User`、`UserPasswordHistory` 导出 | +| `backend/app/admin/model/user.py` | 删除 `dept_id` 字段 | + +### Schema 层 + +| 文件 | 修改内容 | +|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| +| `backend/app/admin/schema/user.py` | 删除 `dept_id`/`roles` 字段、`AddUserRoleParam`/`GetUserInfoWithRelationDetail`/`GetCurrentUserInfoWithRelationDetail`/`AddOAuth2UserParam` 类 | + +### CRUD 层 + +| 文件 | 修改内容 | +|---------------------------------------|-----------------------------------------------------------------------------------------------------------------| +| `backend/app/admin/crud/crud_user.py` | 删除 Role/Dept/Menu/m2m 全部引用,移除 `get_join()` 方法、JoinConfig、m2m 操作;`add()`/`update()`/`delete()`/`get_select()` 简化 | + +### Service 层 + +| 文件 | 修改内容 | +|---------------------------------------------|-----------------------------------------------------------------------------------| +| `backend/app/admin/service/user_service.py` | 删除 `get_roles()` 方法、dept/role 验证逻辑、`dept` 参数;`get_userinfo()` 改用 `user_dao.get()` | +| `backend/app/admin/service/auth_service.py` | 删除 `login_log_service`/`menu_dao` 引用、`get_codes()` 方法、`background_tasks` 参数 | + +### Utils/安全层 + +| 文件 | 修改内容 | +|------------------------------------|------------------------------------------------------------------------------------------------------| +| `backend/app/admin/utils/cache.py` | 删除 `clear_by_role_id()`/`clear_by_menu_id()`/`clear_by_data_scope_id()`/`clear_by_data_rule_id()` 方法 | +| `backend/utils/trace_id.py` | 删除 `OtelTraceIdPlugin` 类 | + +### API 层 + +| 文件 | 修改内容 | +|----------------------------------------------|-------------------------------------------------------------------------------------------------| +| `backend/app/admin/api/v1/sys/user.py` | 删除 `get_user_roles` 路由、`dept` 参数;`delete_user` 改用 `DependsSuperUser`;响应类型改为 `GetUserInfoDetail` | +| `backend/app/admin/api/v1/sys/file.py` | `RequestPermission + DependsRBAC` 改为 `DependsJwtAuth` | +| `backend/app/admin/api/v1/sys/__init__.py` | 仅保留 `user_router`、`file_router`、`plugin_router` | +| `backend/app/admin/api/v1/auth/auth.py` | 删除 `get_codes` 路由、`background_tasks` 参数 | +| `backend/app/admin/api/router.py` | 删除 `monitor_router`、`log_router` | +| `backend/app/router.py` | 删除 `task_v1` | +| `backend/app/admin/api/v1/sys/plugin.py` | `RequestPermission + DependsRBAC` 改为 `DependsSuperUser` | +| `backend/plugin/config/api/v1/sys/config.py` | `RequestPermission + DependsRBAC` 改为 `DependsSuperUser` | + +### JWT/中间件层 + +| 文件 | 修改内容 | +|----------------------------------------------|------------------------------------------------------------------------------------------------------------------| +| `backend/common/security/jwt.py` | `GetUserInfoWithRelationDetail` → `GetUserInfoDetail`;`get_current_user()` 改用 `user_dao.get()`,删除 dept/role 状态检查 | +| `backend/middleware/jwt_auth_middleware.py` | `GetUserInfoWithRelationDetail` → `GetUserInfoDetail` | +| `backend/middleware/opera_log_middleware.py` | 移除 DB 队列/消费者/入库,改为纯控制台日志输出 | +| `backend/middleware/access_middleware.py` | 删除 Prometheus 导入和 2 处计数器调用 | + +### 核心层 + +| 文件 | 修改内容 | +|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| `backend/core/registrar.py` | 删除 socketio/prometheus/otel 导入、`register_socket_app()`、`register_metrics()`、OtelTraceIdPlugin、`create_task(OperaLogMiddleware.consumer())` | +| `backend/core/conf.py` | 删除 CELERY/GRAFANA/DATA_PERMISSION/OAUTH2/EMAIL/WS/CODE_GENERATOR/OPERA_LOG_*/RBAC_ROLE_MENU_* 配置段 | +| `backend/main.py` | 删除插件依赖安装逻辑 | + +### CLI + +| 文件 | 修改内容 | +|------------------|----------------------------------------------------------------------------------| +| `backend/cli.py` | 删除 Celery/插件安装卸载/代码生成相关命令,`FbaCli.subcmd` 简化为 `Init \| Run \| Format \| Alembic` | + +### Enum + +| 文件 | 修改内容 | +|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| `backend/common/enums.py` | 删除 `MenuType`、`MethodType`、`BuildTreeType`、`RoleDataRuleOperatorType`、`RoleDataRuleExpressionType`、`LoginLogStatusType`、`OperaLogCipherType` 枚举 | + +### SQL 初始化数据 + +| 文件 | 修改内容 | +|-------------------------------------------------------|-----------------------------------------------------------------------------------| +| `backend/sql/mysql/init_test_data.sql` | 仅保留 sys_user INSERT(删除 dept/menu/role/role_menu/user_role/data_scope/data_rule 等) | +| `backend/sql/mysql/init_snowflake_test_data.sql` | 同上 | +| `backend/sql/postgresql/init_test_data.sql` | 同上 | +| `backend/sql/postgresql/init_snowflake_test_data.sql` | 同上 | + +### 配置/部署 + +| 文件 | 修改内容 | +|---------------------------------------------|--------------------------------------------------------------------------------------| +| `backend/.env.example` | 删除 Celery/RabbitMQ/OAuth2/Email 环境变量 | +| `pyproject.toml` | 删除 celery/socketio/opentelemetry/prometheus/psutil/dulwich/flower/gevent/aio-pika 依赖 | +| `docker-compose.yml` | 删除 rabbitmq/celery/grafana 全套容器 | +| `Dockerfile` | 简化为单一 server 镜像,删除 celery worker/beat/flower 阶段 | +| `deploy/backend/docker-compose/.env.docker` | 删除 RabbitMQ/Celery/Grafana 端口映射 | +| `deploy/backend/docker-compose/.env.server` | 删除 Celery/OAuth2/Email 环境变量 | + +--- + +## 合并指南 + +从 fba 完整版同步代码到 fba-slim 后,需要关注以下冲突区域: + +### 快速检测 grep 模式 + +合并后运行以下命令,快速找出需要处理的非 slim 引用: + +```bash +# Celery / 任务系统 +grep -rn "celery\|app\.task\|CELERY_" backend/ --include="*.py" | grep -v "__pycache__" + +# Socket.IO +grep -rn "socketio\|common\.socketio\|WS_NO_AUTH" backend/ --include="*.py" | grep -v "__pycache__" + +# 可观测性 +grep -rn "prometheus\|opentelemetry\|otel\|GRAFANA_" backend/ --include="*.py" | grep -v "__pycache__" + +# RBAC (角色/菜单/部门/权限) +grep -rn "DependsRBAC\|RequestPermission\|rbac_verify\|role_menu\|user_role\|dept_dao\|role_dao\|menu_dao\|crud_role\|crud_menu\|crud_dept\|dept_service\|role_service\|menu_service\|GetRoleDetail\|GetDeptDetail\|GetMenuDetail\|build_tree\|GetUserInfoWithRelationDetail\|GetCurrentUserInfoWithRelationDetail\|RBAC_ROLE_MENU" backend/ --include="*.py" | grep -v "__pycache__" + +# 数据权限 +grep -rn "DataRule\|DataScope\|data_rule\|data_scope\|role_data_scope\|data_scope_rule\|DataPermissionFilter\|filter_data_permission\|is_filter_scopes" backend/ --include="*.py" | grep -v "__pycache__" + +# 已移除的插件 +grep -rn "plugin\.dict\|plugin\.email\|plugin\.notice\|plugin\.oauth2\|plugin\.code_generator" backend/ --include="*.py" | grep -v "__pycache__" + +# 监控 API +grep -rn "monitor_router\|api/v1/monitor\|sys:monitor" backend/ --include="*.py" | grep -v "__pycache__" + +# 邮箱验证码 +grep -rn "EMAIL_CAPTCHA_REDIS_PREFIX\|CACHE_DICT_REDIS_PREFIX" backend/ --include="*.py" | grep -v "__pycache__" + +# psutil (服务器监控) +grep -rn "import psutil" backend/ --include="*.py" | grep -v "__pycache__" + +# 日志系统 (DB 日志) +grep -rn "LoginLog\|OperaLog\|login_log\|opera_log\|opera_log_service\|login_log_service\|OPERA_LOG_\|batch_dequeue\|opera_log_queue" backend/ --include="*.py" | grep -v "__pycache__" +``` + +### 高冲突文件 + +以下文件在完整版更新时最容易产生冲突: + +1. **`backend/core/conf.py`** — 配置字段差异最大 +2. **`backend/core/registrar.py`** — 中间件和组件注册差异 +3. **`backend/cli.py`** — CLI 命令结构差异大 +4. **`backend/main.py`** — 插件检测逻辑已保留 +5. **`backend/common/security/jwt.py`** — `GetUserInfoDetail` vs `GetUserInfoWithRelationDetail`,`get_current_user()` 差异 +6. **`backend/app/admin/crud/crud_user.py`** — 无 get_join/JoinConfig/m2m 操作 +7. **`backend/app/admin/service/auth_service.py`** — 登录日志、menu_dao、background_tasks 差异 +8. **`backend/middleware/opera_log_middleware.py`** — 完整版有 DB 队列,slim 版仅控制台 +9. **`backend/middleware/jwt_auth_middleware.py`** — schema 类型差异 +10. **`pyproject.toml`** — 依赖列表差异 +11. **`docker-compose.yml`** — 容器编排差异 +12. **`Dockerfile`** — 构建阶段差异 + +### 合并策略 + +1. **优先接受 slim 版本**的文件:`conf.py`、`registrar.py`、`cli.py`、`main.py`、`Dockerfile`、`docker-compose.yml` +2. **需要手动合并**的文件:CRUD/Service/API 层(可能有新增功能需要保留,但需移除 RBAC/数据权限/可观测性引用) +3. **直接接受完整版**的文件:不涉及上述移除功能的纯业务逻辑改动 +4. **合并后运行上述 grep 命令**清理残留引用 +5. **运行 `uv lock` 和导入检查**确认无依赖/导入错误 diff --git a/backend/__init__.py b/backend/__init__.py index 65498b6..7eeb0ed 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -14,4 +14,4 @@ globals()[class_name] = cls -__version__ = '1.12.0' +__version__ = '1.13.1' diff --git a/backend/alembic.ini b/backend/alembic.ini index 912b03d..28e6085 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -1,84 +1,3 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts. -# Use forward slashes (/) also on windows to provide an os agnostic path -script_location = alembic - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d_%%(minute).2d_%%(second).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = . - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python>=3.9 or backports.zoneinfo library. -# Any required deps can installed by adding `alembic[tz]` to the pip requirements -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. -# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. -# Valid values for version_path_separator are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -# version_path_separator = newline -version_path_separator = os # Use os.pathsep. Default configuration used for new projects. - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = driver://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the exec runner, execute a binary -# hooks = ruff -# ruff.type = exec -# ruff.executable = %(here)s/.venv/bin/ruff -# ruff.options = --fix REVISION_SCRIPT_FILENAME - # Logging configuration [loggers] keys = root,sqlalchemy,alembic diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 1e4b865..58a4ffe 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -10,6 +10,7 @@ from backend.common.model import MappedBase from backend.core import path_conf +from backend.core.path_conf import BASE_PATH from backend.database.db import SQLALCHEMY_DATABASE_URL if not os.path.exists(path_conf.ALEMBIC_VERSION_DIR): @@ -17,19 +18,22 @@ # this is the Alembic Config object, which provides # access to the values within the .ini file in use. -alembic_config = context.config +config = context.config # Interpret the config file for Python logging. # This line sets up loggers basically. -if alembic_config.config_file_name is not None: - fileConfig(alembic_config.config_file_name) +if config.config_file_name is not None: + fileConfig(BASE_PATH / config.config_file_name) -# model's MetaData object +# add your model's MetaData object here # for 'autogenerate' support target_metadata = MappedBase.metadata # other values from the config, defined by the needs of env.py, -alembic_config.set_main_option( +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. +config.set_main_option( 'sqlalchemy.url', SQLALCHEMY_DATABASE_URL.render_as_string(hide_password=False).replace('%', '%%'), ) @@ -47,7 +51,7 @@ def run_migrations_offline() -> None: script output. """ - url = alembic_config.get_main_option('sqlalchemy.url') + url = config.get_main_option('sqlalchemy.url') context.configure( url=url, target_metadata=target_metadata, @@ -63,9 +67,9 @@ def run_migrations_offline() -> None: def do_run_migrations(connection: Connection) -> None: - # 当迁移无变化时,不生成迁移记录 def process_revision_directives(context, revision, directives) -> None: # noqa: ANN001 - if alembic_config.cmd_opts.autogenerate: + """当迁移无变化时,不生成迁移记录""" + if config.cmd_opts.autogenerate: script = directives[0] if script.upgrade_ops.is_empty(): directives[:] = [] @@ -91,7 +95,7 @@ async def run_async_migrations() -> None: """ connectable = async_engine_from_config( - alembic_config.get_section(alembic_config.config_ini_section, {}), + config.get_section(config.config_ini_section, {}), prefix='sqlalchemy.', poolclass=pool.NullPool, ) diff --git a/backend/app/admin/api/router.py b/backend/app/admin/api/router.py index 2db6e78..ad8a1ce 100644 --- a/backend/app/admin/api/router.py +++ b/backend/app/admin/api/router.py @@ -1,7 +1,6 @@ from fastapi import APIRouter from backend.app.admin.api.v1.auth import router as auth_router -from backend.app.admin.api.v1.log import router as log_router from backend.app.admin.api.v1.sys import router as sys_router from backend.core.conf import settings @@ -9,4 +8,3 @@ v1.include_router(auth_router) v1.include_router(sys_router) -v1.include_router(log_router) diff --git a/backend/app/admin/api/v1/auth/auth.py b/backend/app/admin/api/v1/auth/auth.py index fc9c8a1..1d527ca 100644 --- a/backend/app/admin/api/v1/auth/auth.py +++ b/backend/app/admin/api/v1/auth/auth.py @@ -2,14 +2,14 @@ from fastapi import APIRouter, Depends, Request, Response from fastapi.security import HTTPBasicCredentials -from fastapi_limiter.depends import RateLimiter -from starlette.background import BackgroundTasks +from pyrate_limiter import Duration, Rate from backend.app.admin.schema.token import GetLoginToken, GetNewToken, GetSwaggerToken from backend.app.admin.schema.user import AuthLoginParam from backend.app.admin.service.auth_service import auth_service from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base from backend.database.db import CurrentSession, CurrentSessionTransaction +from backend.utils.limiter import RateLimiter router = APIRouter() @@ -26,15 +26,14 @@ async def login_swagger( '/login', summary='用户登录', description='json 格式登录, 仅支持在第三方api工具调试, 例如: postman', - dependencies=[Depends(RateLimiter(times=5, minutes=1))], + dependencies=[Depends(RateLimiter(Rate(5, Duration.MINUTE)))], ) async def login( db: CurrentSessionTransaction, response: Response, obj: AuthLoginParam, - background_tasks: BackgroundTasks, ) -> ResponseSchemaModel[GetLoginToken]: - data = await auth_service.login(db=db, response=response, obj=obj, background_tasks=background_tasks) + data = await auth_service.login(db=db, response=response, obj=obj) return response_base.success(data=data) diff --git a/backend/app/admin/api/v1/auth/captcha.py b/backend/app/admin/api/v1/auth/captcha.py index f7d6323..e99957c 100644 --- a/backend/app/admin/api/v1/auth/captcha.py +++ b/backend/app/admin/api/v1/auth/captcha.py @@ -2,7 +2,7 @@ from fast_captcha import img_captcha from fastapi import APIRouter, Depends -from fastapi_limiter.depends import RateLimiter +from pyrate_limiter import Duration, Rate from starlette.concurrency import run_in_threadpool from backend.app.admin.schema.captcha import GetCaptchaDetail @@ -11,6 +11,7 @@ from backend.database.db import CurrentSession from backend.database.redis import redis_client from backend.utils.dynamic_config import load_login_config +from backend.utils.limiter import RateLimiter router = APIRouter() @@ -18,7 +19,7 @@ @router.get( '/captcha', summary='获取登录验证码', - dependencies=[Depends(RateLimiter(times=5, seconds=10))], + dependencies=[Depends(RateLimiter(Rate(5, Duration.SECOND * 30)))], ) async def get_captcha(db: CurrentSession) -> ResponseSchemaModel[GetCaptchaDetail]: await load_login_config(db) diff --git a/backend/app/admin/api/v1/log/__init__.py b/backend/app/admin/api/v1/log/__init__.py deleted file mode 100644 index ec3f9ba..0000000 --- a/backend/app/admin/api/v1/log/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import APIRouter - -from backend.app.admin.api.v1.log.login_log import router as login_log -from backend.app.admin.api.v1.log.opera_log import router as opera_log - -router = APIRouter(prefix='/logs') - -router.include_router(login_log, prefix='/login', tags=['登录日志']) -router.include_router(opera_log, prefix='/opera', tags=['操作日志']) diff --git a/backend/app/admin/api/v1/log/login_log.py b/backend/app/admin/api/v1/log/login_log.py deleted file mode 100644 index 49c8811..0000000 --- a/backend/app/admin/api/v1/log/login_log.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Annotated - -from fastapi import APIRouter, Query - -from backend.app.admin.schema.login_log import DeleteLoginLogParam, GetLoginLogDetail -from backend.app.admin.service.login_log_service import login_log_service -from backend.common.pagination import DependsPagination, PageData -from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base -from backend.common.security.jwt import DependsJwtAuth -from backend.database.db import CurrentSession, CurrentSessionTransaction - -router = APIRouter() - - -@router.get( - '', - summary='分页获取登录日志', - dependencies=[ - DependsJwtAuth, - DependsPagination, - ], -) -async def get_login_logs_paginated( - db: CurrentSession, - username: Annotated[str | None, Query(description='用户名')] = None, - status: Annotated[int | None, Query(description='状态')] = None, - ip: Annotated[str | None, Query(description='IP 地址')] = None, -) -> ResponseSchemaModel[PageData[GetLoginLogDetail]]: - page_data = await login_log_service.get_list(db=db, username=username, status=status, ip=ip) - - return response_base.success(data=page_data) - - -@router.delete( - '', - summary='批量删除登录日志', - dependencies=[DependsJwtAuth], -) -async def delete_login_logs(db: CurrentSessionTransaction, obj: DeleteLoginLogParam) -> ResponseModel: - count = await login_log_service.delete(db=db, obj=obj) - if count > 0: - return response_base.success() - return response_base.fail() - - -@router.delete( - '/all', - summary='清空登录日志', - dependencies=[DependsJwtAuth], -) -async def delete_all_login_logs(db: CurrentSessionTransaction) -> ResponseModel: - await login_log_service.delete_all(db=db) - return response_base.success() diff --git a/backend/app/admin/api/v1/log/opera_log.py b/backend/app/admin/api/v1/log/opera_log.py deleted file mode 100644 index f83d522..0000000 --- a/backend/app/admin/api/v1/log/opera_log.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Annotated - -from fastapi import APIRouter, Query - -from backend.app.admin.schema.opera_log import DeleteOperaLogParam, GetOperaLogDetail -from backend.app.admin.service.opera_log_service import opera_log_service -from backend.common.pagination import DependsPagination, PageData -from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base -from backend.common.security.jwt import DependsJwtAuth -from backend.database.db import CurrentSession, CurrentSessionTransaction - -router = APIRouter() - - -@router.get( - '', - summary='分页获取操作日志', - dependencies=[ - DependsJwtAuth, - DependsPagination, - ], -) -async def get_opera_logs_paginated( - db: CurrentSession, - username: Annotated[str | None, Query(description='用户名')] = None, - status: Annotated[int | None, Query(description='状态')] = None, - ip: Annotated[str | None, Query(description='IP 地址')] = None, -) -> ResponseSchemaModel[PageData[GetOperaLogDetail]]: - page_data = await opera_log_service.get_list(db=db, username=username, status=status, ip=ip) - - return response_base.success(data=page_data) - - -@router.delete( - '', - summary='批量删除操作日志', - dependencies=[DependsJwtAuth], -) -async def delete_opera_logs(db: CurrentSessionTransaction, obj: DeleteOperaLogParam) -> ResponseModel: - count = await opera_log_service.delete(db=db, obj=obj) - if count > 0: - return response_base.success() - return response_base.fail() - - -@router.delete( - '/all', - summary='清空操作日志', - dependencies=[DependsJwtAuth], -) -async def delete_all_opera_logs(db: CurrentSessionTransaction) -> ResponseModel: - await opera_log_service.delete_all(db=db) - return response_base.success() diff --git a/backend/app/admin/api/v1/sys/__init__.py b/backend/app/admin/api/v1/sys/__init__.py index ac513dd..9bb64ee 100644 --- a/backend/app/admin/api/v1/sys/__init__.py +++ b/backend/app/admin/api/v1/sys/__init__.py @@ -1,7 +1,11 @@ from fastapi import APIRouter +from backend.app.admin.api.v1.sys.file import router as file_router +from backend.app.admin.api.v1.sys.plugin import router as plugin_router from backend.app.admin.api.v1.sys.user import router as user_router router = APIRouter(prefix='/sys') router.include_router(user_router, prefix='/users', tags=['系统用户']) +router.include_router(file_router, prefix='/files', tags=['系统文件']) +router.include_router(plugin_router, prefix='/plugins', tags=['系统插件']) diff --git a/backend/app/admin/api/v1/sys/file.py b/backend/app/admin/api/v1/sys/file.py new file mode 100644 index 0000000..3ffbc7e --- /dev/null +++ b/backend/app/admin/api/v1/sys/file.py @@ -0,0 +1,21 @@ +from typing import Annotated + +from fastapi import APIRouter, File, UploadFile + +from backend.common.dataclasses import UploadUrl +from backend.common.response.response_schema import ResponseSchemaModel, response_base +from backend.common.security.jwt import DependsJwtAuth +from backend.utils.file_ops import upload_file, upload_file_verify + +router = APIRouter() + + +@router.post( + '/upload', + summary='本地文件上传', + dependencies=[DependsJwtAuth], +) +async def upload_files(file: Annotated[UploadFile, File()]) -> ResponseSchemaModel[UploadUrl]: + upload_file_verify(file) + filename = await upload_file(file) + return response_base.success(data={'url': f'/static/upload/{filename}'}) diff --git a/backend/app/admin/api/v1/sys/plugin.py b/backend/app/admin/api/v1/sys/plugin.py new file mode 100644 index 0000000..0bf6047 --- /dev/null +++ b/backend/app/admin/api/v1/sys/plugin.py @@ -0,0 +1,78 @@ +from typing import Annotated, Any + +from fastapi import APIRouter, File, Path, UploadFile +from fastapi.params import Query +from starlette.responses import StreamingResponse + +from backend.app.admin.service.plugin_service import plugin_service +from backend.common.enums import PluginType +from backend.common.response.response_code import CustomResponse +from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base +from backend.common.security.jwt import DependsJwtAuth, DependsSuperUser + +router = APIRouter() + + +@router.get('', summary='获取所有插件', dependencies=[DependsJwtAuth]) +async def get_all_plugins() -> ResponseSchemaModel[list[dict[str, Any]]]: + plugins = await plugin_service.get_all() + return response_base.success(data=plugins) + + +@router.get('/changed', summary='是否存在插件变更', dependencies=[DependsJwtAuth]) +async def plugin_changed() -> ResponseSchemaModel[bool]: + plugins = await plugin_service.changed() + return response_base.success(data=bool(plugins)) + + +@router.post( + '', + summary='安装插件', + description='使用插件 zip 压缩包或 git 仓库地址进行安装(仅开发环境)', + dependencies=[DependsSuperUser], +) +async def install_plugin( + type: Annotated[PluginType, Query(description='插件类型')], + file: Annotated[UploadFile | None, File()] = None, + repo_url: Annotated[str | None, Query(description='插件 git 仓库地址')] = None, +) -> ResponseModel: + plugin_name = await plugin_service.install(type=type, file=file, repo_url=repo_url) + return response_base.success( + res=CustomResponse( + code=200, + msg=f'插件 {plugin_name} 安装成功,请根据插件说明(README.md)进行相关配置并重启服务', + ), + ) + + +@router.delete( + '/{plugin}', + summary='卸载插件', + description='此操作会直接删除插件依赖,但不会直接删除插件,而是将插件移动到备份目录(仅开发环境)', + dependencies=[DependsSuperUser], +) +async def uninstall_plugin(plugin: Annotated[str, Path(description='插件名称')]) -> ResponseModel: + await plugin_service.uninstall(plugin=plugin) + return response_base.success( + res=CustomResponse(code=200, msg=f'插件 {plugin} 卸载成功,请根据插件说明(README.md)移除相关配置并重启服务'), + ) + + +@router.put( + '/{plugin}/status', + summary='更新插件状态', + dependencies=[DependsSuperUser], +) +async def update_plugin_status(plugin: Annotated[str, Path(description='插件名称')]) -> ResponseModel: + await plugin_service.update_status(plugin=plugin) + return response_base.success() + + +@router.get('/{plugin}', summary='下载插件', dependencies=[DependsJwtAuth]) +async def download_plugin(plugin: Annotated[str, Path(description='插件名称')]) -> StreamingResponse: + bio = await plugin_service.build(plugin=plugin) + return StreamingResponse( + bio, + media_type='application/x-zip-compressed', + headers={'Content-Disposition': f'attachment; filename={plugin}.zip'}, + ) diff --git a/backend/app/admin/api/v1/sys/user.py b/backend/app/admin/api/v1/sys/user.py index 9cd3763..b48e158 100644 --- a/backend/app/admin/api/v1/sys/user.py +++ b/backend/app/admin/api/v1/sys/user.py @@ -4,8 +4,7 @@ from backend.app.admin.schema.user import ( AddUserParam, - GetCurrentUserInfoWithRelationDetail, - GetUserInfoWithRelationDetail, + GetUserInfoDetail, ResetPasswordParam, UpdateUserParam, ) @@ -20,7 +19,7 @@ @router.get('/me', summary='获取当前用户信息', dependencies=[DependsJwtAuth]) -async def get_current_user(request: Request) -> ResponseSchemaModel[GetCurrentUserInfoWithRelationDetail]: +async def get_current_user(request: Request) -> ResponseSchemaModel[GetUserInfoDetail]: data = request.user.model_dump() return response_base.success(data=data) @@ -29,7 +28,7 @@ async def get_current_user(request: Request) -> ResponseSchemaModel[GetCurrentUs async def get_userinfo( db: CurrentSession, pk: Annotated[int, Path(description='用户 ID')], -) -> ResponseSchemaModel[GetUserInfoWithRelationDetail]: +) -> ResponseSchemaModel[GetUserInfoDetail]: data = await user_service.get_userinfo(db=db, pk=pk) return response_base.success(data=data) @@ -47,15 +46,13 @@ async def get_users_paginated( username: Annotated[str | None, Query(description='用户名')] = None, phone: Annotated[str | None, Query(description='手机号')] = None, status: Annotated[int | None, Query(description='状态')] = None, -) -> ResponseSchemaModel[PageData[GetUserInfoWithRelationDetail]]: +) -> ResponseSchemaModel[PageData[GetUserInfoDetail]]: page_data = await user_service.get_list(db=db, username=username, phone=phone, status=status) return response_base.success(data=page_data) @router.post('', summary='创建用户', dependencies=[DependsSuperUser]) -async def create_user( - db: CurrentSessionTransaction, obj: AddUserParam -) -> ResponseSchemaModel[GetUserInfoWithRelationDetail]: +async def create_user(db: CurrentSessionTransaction, obj: AddUserParam) -> ResponseSchemaModel[GetUserInfoDetail]: await user_service.create(db=db, obj=obj) data = await user_service.get_userinfo(db=db, username=obj.username) return response_base.success(data=data) @@ -136,10 +133,9 @@ async def update_user_avatar( async def update_user_email( db: CurrentSessionTransaction, request: Request, - captcha: Annotated[str, Body(embed=True, description='邮箱验证码')], email: Annotated[str, Body(embed=True, description='用户邮箱')], ) -> ResponseModel: - count = await user_service.update_email(db=db, user_id=request.user.id, captcha=captcha, email=email) + count = await user_service.update_email(db=db, user_id=request.user.id, email=email) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/app/admin/crud/crud_login_log.py b/backend/app/admin/crud/crud_login_log.py deleted file mode 100644 index b658a76..0000000 --- a/backend/app/admin/crud/crud_login_log.py +++ /dev/null @@ -1,64 +0,0 @@ -from sqlalchemy import Select -from sqlalchemy import delete as sa_delete -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy_crud_plus import CRUDPlus - -from backend.app.admin.model import LoginLog -from backend.app.admin.schema.login_log import CreateLoginLogParam - - -class CRUDLoginLog(CRUDPlus[LoginLog]): - """登录日志数据库操作类""" - - async def get_select(self, username: str | None, status: int | None, ip: str | None) -> Select: - """ - 获取登录日志列表查询表达式 - - :param username: 用户名 - :param status: 登录状态 - :param ip: IP 地址 - :return: - """ - filters = {} - - if username is not None: - filters['username__like'] = f'%{username}%' - if status is not None: - filters['status'] = status - if ip is not None: - filters['ip__like'] = f'%{ip}%' - - return await self.select_order('created_time', 'desc', **filters) - - async def create(self, db: AsyncSession, obj: CreateLoginLogParam) -> None: - """ - 创建登录日志 - - :param db: 数据库会话 - :param obj: 创建登录日志参数 - :return: - """ - await self.create_model(db, obj, commit=True) - - async def delete(self, db: AsyncSession, pks: list[int]) -> int: - """ - 批量删除登录日志 - - :param db: 数据库会话 - :param pks: 登录日志 ID 列表 - :return: - """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) - - @staticmethod - async def delete_all(db: AsyncSession) -> None: - """ - 删除所有日志 - - :param db: 数据库会话 - :return: - """ - await db.execute(sa_delete(LoginLog)) - - -login_log_dao: CRUDLoginLog = CRUDLoginLog(LoginLog) diff --git a/backend/app/admin/crud/crud_opera_log.py b/backend/app/admin/crud/crud_opera_log.py deleted file mode 100644 index 900cc8c..0000000 --- a/backend/app/admin/crud/crud_opera_log.py +++ /dev/null @@ -1,74 +0,0 @@ -from sqlalchemy import Select -from sqlalchemy import delete as sa_delete -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy_crud_plus import CRUDPlus - -from backend.app.admin.model import OperaLog -from backend.app.admin.schema.opera_log import CreateOperaLogParam - - -class CRUDOperaLogDao(CRUDPlus[OperaLog]): - """操作日志数据库操作类""" - - async def get_select(self, username: str | None, status: int | None, ip: str | None) -> Select: - """ - 获取操作日志列表查询表达式 - - :param username: 用户名 - :param status: 操作状态 - :param ip: IP 地址 - :return: - """ - filters = {} - - if username is not None: - filters['username__like'] = f'%{username}%' - if status is not None: - filters['status__eq'] = status - if ip is not None: - filters['ip__like'] = f'%{ip}%' - - return await self.select_order('created_time', 'desc', **filters) - - async def create(self, db: AsyncSession, obj: CreateOperaLogParam) -> None: - """ - 创建操作日志 - - :param db: 数据库会话 - :param obj: 操作日志创建参数 - :return: - """ - await self.create_model(db, obj) - - async def bulk_create(self, db: AsyncSession, objs: list[CreateOperaLogParam]) -> None: - """ - 批量创建操作日志 - - :param db: 数据库会话 - :param objs: 操作日志创建参数列表 - :return: - """ - await self.create_models(db, objs) - - async def delete(self, db: AsyncSession, pks: list[int]) -> int: - """ - 批量删除操作日志 - - :param db: 数据库会话 - :param pks: 操作日志 ID 列表 - :return: - """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) - - @staticmethod - async def delete_all(db: AsyncSession) -> None: - """ - 删除所有日志 - - :param db: 数据库会话 - :return: - """ - await db.execute(sa_delete(OperaLog)) - - -opera_log_dao: CRUDOperaLogDao = CRUDOperaLogDao(OperaLog) diff --git a/backend/app/admin/crud/crud_user.py b/backend/app/admin/crud/crud_user.py index 6ac8a89..596ba45 100644 --- a/backend/app/admin/crud/crud_user.py +++ b/backend/app/admin/crud/crud_user.py @@ -6,17 +6,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy_crud_plus import CRUDPlus -from backend.app.admin.model import ( - User, -) -from backend.app.admin.schema.user import ( - AddOAuth2UserParam, - AddUserParam, - UpdateUserParam, -) +from backend.app.admin.model import User +from backend.app.admin.schema.user import AddUserParam, UpdateUserParam from backend.app.admin.utils.password_security import get_hash_password -from backend.utils.dynamic_import import import_module_cached -from backend.utils.serializers import select_join_serialize from backend.utils.timezone import timezone @@ -72,7 +64,7 @@ async def get_select(self, username: str | None, phone: str | None, status: int :param status: 用户状态 :return: """ - filters = {} + filters: dict[str, Any] = {} if username: filters['username__like'] = f'%{username}%' @@ -81,11 +73,7 @@ async def get_select(self, username: str | None, phone: str | None, status: int if status is not None: filters['status'] = status - return await self.select_order( - 'id', - 'desc', - **filters, - ) + return await self.select_order('id', 'desc', **filters) async def add(self, db: AsyncSession, obj: AddUserParam) -> None: """ @@ -98,25 +86,10 @@ async def add(self, db: AsyncSession, obj: AddUserParam) -> None: salt = bcrypt.gensalt() obj.password = get_hash_password(obj.password, salt) - dict_obj = obj.model_dump(exclude={'roles'}) - dict_obj.update({'salt': salt}) - new_user = self.model(**dict_obj) - db.add(new_user) - await db.flush() - - async def add_by_oauth2(self, db: AsyncSession, obj: AddOAuth2UserParam) -> None: - """ - 通过 OAuth2 添加用户 - - :param db: 数据库会话 - :param obj: 注册用户参数 - :return: - """ dict_obj = obj.model_dump() - dict_obj.update({'is_staff': True, 'salt': None}) + dict_obj.update({'salt': salt}) new_user = self.model(**dict_obj) db.add(new_user) - await db.flush() async def update(self, db: AsyncSession, user_id: int, obj: UpdateUserParam) -> int: """ @@ -127,8 +100,7 @@ async def update(self, db: AsyncSession, user_id: int, obj: UpdateUserParam) -> :param obj: 更新用户参数 :return: """ - count = await self.update_model(db, user_id, obj) - return count + return await self.update_model(db, user_id, obj) async def update_login_time(self, db: AsyncSession, username: str) -> int: """ @@ -248,46 +220,7 @@ async def delete(self, db: AsyncSession, user_id: int) -> int: :param user_id: 用户 ID :return: """ - try: - user_social = import_module_cached('backend.plugin.oauth2.crud.crud_user_social') - user_social_dao = user_social.user_social_dao - except (ImportError, AttributeError): - pass - else: - await user_social_dao.delete_by_user_id(db, user_id) - return await self.delete_model(db, user_id) - async def get_join( - self, - db: AsyncSession, - *, - user_id: int | None = None, - username: str | None = None, - ) -> Any | None: - """ - 获取用户关联信息 - - :param db: 数据库会话 - :param user_id: 用户 ID - :param username: 用户名 - :return: - """ - filters = {} - - if user_id: - filters['id'] = user_id - if username: - filters['username'] = username - - result = await self.select_models( - db, - **filters, - ) - - return select_join_serialize( - result, - ) - user_dao: CRUDUser = CRUDUser(User) diff --git a/backend/app/admin/crud/crud_user_password_history.py b/backend/app/admin/crud/crud_user_password_history.py new file mode 100644 index 0000000..ee0ab4d --- /dev/null +++ b/backend/app/admin/crud/crud_user_password_history.py @@ -0,0 +1,34 @@ +from collections.abc import Sequence + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy_crud_plus import CRUDPlus + +from backend.app.admin.model.user_password_history import UserPasswordHistory +from backend.app.admin.schema.user_password_history import CreateUserPasswordHistoryParam + + +class CRUDUserPasswordHistory(CRUDPlus[UserPasswordHistory]): + """用户密码历史记录数据库操作类""" + + async def create(self, db: AsyncSession, obj: CreateUserPasswordHistoryParam) -> None: + """ + 创建密码历史记录 + + :param db: 数据库会话 + :param obj: 创建密码历史记录参数 + :return: + """ + await self.create_model(db, obj) + + async def get_by_user_id(self, db: AsyncSession, user_id: int) -> Sequence[UserPasswordHistory]: + """ + 获取用户的密码历史记录 + + :param db: 数据库会话 + :param user_id: 用户 ID + :return: + """ + return await self.select_models_order(db, 'id', 'desc', self.model.user_id == user_id) + + +user_password_history_dao: CRUDUserPasswordHistory = CRUDUserPasswordHistory(UserPasswordHistory) diff --git a/backend/app/admin/model/__init__.py b/backend/app/admin/model/__init__.py index 2958b6b..0fb2a90 100644 --- a/backend/app/admin/model/__init__.py +++ b/backend/app/admin/model/__init__.py @@ -1,3 +1,2 @@ -from backend.app.admin.model.login_log import LoginLog as LoginLog -from backend.app.admin.model.opera_log import OperaLog as OperaLog from backend.app.admin.model.user import User as User +from backend.app.admin.model.user_password_history import UserPasswordHistory as UserPasswordHistory diff --git a/backend/app/admin/model/login_log.py b/backend/app/admin/model/login_log.py deleted file mode 100644 index 0726b39..0000000 --- a/backend/app/admin/model/login_log.py +++ /dev/null @@ -1,35 +0,0 @@ -from datetime import datetime - -import sqlalchemy as sa - -from sqlalchemy.orm import Mapped, mapped_column - -from backend.common.model import DataClassBase, TimeZone, UniversalText, id_key -from backend.utils.timezone import timezone - - -class LoginLog(DataClassBase): - """登录日志表""" - - __tablename__ = 'sys_login_log' - - id: Mapped[id_key] = mapped_column(init=False) - user_uuid: Mapped[str] = mapped_column(sa.String(64), comment='用户UUID') - username: Mapped[str] = mapped_column(sa.String(64), comment='用户名') - status: Mapped[int] = mapped_column(insert_default=0, comment='登录状态(0失败 1成功)') - ip: Mapped[str] = mapped_column(sa.String(64), comment='登录IP地址') - country: Mapped[str | None] = mapped_column(sa.String(64), comment='国家') - region: Mapped[str | None] = mapped_column(sa.String(64), comment='地区') - city: Mapped[str | None] = mapped_column(sa.String(64), comment='城市') - user_agent: Mapped[str | None] = mapped_column(sa.String(512), comment='请求头') - os: Mapped[str | None] = mapped_column(sa.String(64), comment='操作系统') - browser: Mapped[str | None] = mapped_column(sa.String(64), comment='浏览器') - device: Mapped[str | None] = mapped_column(sa.String(64), comment='设备') - msg: Mapped[str] = mapped_column(UniversalText, comment='提示消息') - login_time: Mapped[datetime] = mapped_column(TimeZone, comment='登录时间') - created_time: Mapped[datetime] = mapped_column( - TimeZone, - init=False, - default_factory=timezone.now, - comment='创建时间', - ) diff --git a/backend/app/admin/model/opera_log.py b/backend/app/admin/model/opera_log.py deleted file mode 100644 index fd114b9..0000000 --- a/backend/app/admin/model/opera_log.py +++ /dev/null @@ -1,38 +0,0 @@ -from datetime import datetime - -import sqlalchemy as sa - -from sqlalchemy.orm import Mapped, mapped_column - -from backend.common.model import DataClassBase, TimeZone, UniversalText, id_key -from backend.utils.timezone import timezone - - -class OperaLog(DataClassBase): - """操作日志表""" - - __tablename__ = 'sys_opera_log' - - id: Mapped[id_key] = mapped_column(init=False) - trace_id: Mapped[str] = mapped_column(sa.String(32), comment='请求跟踪 ID') - username: Mapped[str | None] = mapped_column(sa.String(64), comment='用户名') - method: Mapped[str] = mapped_column(sa.String(32), comment='请求类型') - title: Mapped[str] = mapped_column(sa.String(256), comment='操作模块') - path: Mapped[str] = mapped_column(sa.String(512), comment='请求路径') - ip: Mapped[str] = mapped_column(sa.String(64), comment='IP地址') - country: Mapped[str | None] = mapped_column(sa.String(64), comment='国家') - region: Mapped[str | None] = mapped_column(sa.String(64), comment='地区') - city: Mapped[str | None] = mapped_column(sa.String(64), comment='城市') - user_agent: Mapped[str | None] = mapped_column(sa.String(512), comment='请求头') - os: Mapped[str | None] = mapped_column(sa.String(64), comment='操作系统') - browser: Mapped[str | None] = mapped_column(sa.String(64), comment='浏览器') - device: Mapped[str | None] = mapped_column(sa.String(64), comment='设备') - args: Mapped[str | None] = mapped_column(sa.JSON(), comment='请求参数') - status: Mapped[int] = mapped_column(comment='操作状态(0异常 1正常)') - code: Mapped[str] = mapped_column(sa.String(32), insert_default='200', comment='操作状态码') - msg: Mapped[str | None] = mapped_column(UniversalText, comment='提示消息') - cost_time: Mapped[float] = mapped_column(insert_default=0.0, comment='请求耗时(ms)') - opera_time: Mapped[datetime] = mapped_column(TimeZone, comment='操作时间') - created_time: Mapped[datetime] = mapped_column( - TimeZone, init=False, default_factory=timezone.now, comment='创建时间' - ) diff --git a/backend/app/admin/model/user_password_history.py b/backend/app/admin/model/user_password_history.py new file mode 100644 index 0000000..11d0767 --- /dev/null +++ b/backend/app/admin/model/user_password_history.py @@ -0,0 +1,24 @@ +from datetime import datetime + +import sqlalchemy as sa + +from sqlalchemy.orm import Mapped, mapped_column + +from backend.common.model import DataClassBase, TimeZone, id_key +from backend.utils.timezone import timezone + + +class UserPasswordHistory(DataClassBase): + """用户密码历史记录表""" + + __tablename__ = 'sys_user_password_history' + + id: Mapped[id_key] = mapped_column(init=False) + user_id: Mapped[int] = mapped_column(sa.BigInteger, index=True, comment='用户 ID') + password: Mapped[str] = mapped_column(sa.String(256), comment='历史密码') + created_time: Mapped[datetime] = mapped_column( + TimeZone, + init=False, + default_factory=timezone.now, + comment='创建时间', + ) diff --git a/backend/app/admin/schema/login_log.py b/backend/app/admin/schema/login_log.py deleted file mode 100644 index 7b82920..0000000 --- a/backend/app/admin/schema/login_log.py +++ /dev/null @@ -1,46 +0,0 @@ -from datetime import datetime - -from pydantic import ConfigDict, Field - -from backend.common.schema import SchemaBase - - -class LoginLogSchemaBase(SchemaBase): - """登录日志基础模型""" - - user_uuid: str = Field(description='用户 UUID') - username: str = Field(description='用户名') - status: int = Field(description='登录状态') - ip: str = Field(description='IP 地址') - country: str | None = Field(None, description='国家') - region: str | None = Field(None, description='地区') - city: str | None = Field(None, description='城市') - user_agent: str | None = Field(description='用户代理') - browser: str | None = Field(None, description='浏览器') - os: str | None = Field(None, description='操作系统') - device: str | None = Field(None, description='设备') - msg: str = Field(description='消息') - login_time: datetime = Field(description='登录时间') - - -class CreateLoginLogParam(LoginLogSchemaBase): - """创建登录日志参数""" - - -class UpdateLoginLogParam(LoginLogSchemaBase): - """更新登录日志参数""" - - -class DeleteLoginLogParam(SchemaBase): - """删除登录日志参数""" - - pks: list[int] = Field(description='登录日志 ID 列表') - - -class GetLoginLogDetail(LoginLogSchemaBase): - """登录日志详情""" - - model_config = ConfigDict(from_attributes=True) - - id: int = Field(description='日志 ID') - created_time: datetime = Field(description='创建时间') diff --git a/backend/app/admin/schema/opera_log.py b/backend/app/admin/schema/opera_log.py deleted file mode 100644 index 7ac1de3..0000000 --- a/backend/app/admin/schema/opera_log.py +++ /dev/null @@ -1,54 +0,0 @@ -from datetime import datetime -from typing import Any - -from pydantic import ConfigDict, Field - -from backend.common.enums import StatusType -from backend.common.schema import SchemaBase - - -class OperaLogSchemaBase(SchemaBase): - """操作日志基础模型""" - - trace_id: str = Field(description='追踪 ID') - username: str | None = Field(None, description='用户名') - method: str = Field(description='请求方法') - title: str = Field(description='操作标题') - path: str = Field(description='请求路径') - ip: str = Field(description='IP 地址') - country: str | None = Field(None, description='国家') - region: str | None = Field(None, description='地区') - city: str | None = Field(None, description='城市') - user_agent: str | None = Field(description='用户代理') - os: str | None = Field(None, description='操作系统') - browser: str | None = Field(None, description='浏览器') - device: str | None = Field(None, description='设备') - args: dict[str, Any] | None = Field(None, description='请求参数') - status: StatusType = Field(description='状态') - code: str = Field(description='状态码') - msg: str | None = Field(None, description='消息') - cost_time: float = Field(description='耗时') - opera_time: datetime = Field(description='操作时间') - - -class CreateOperaLogParam(OperaLogSchemaBase): - """创建操作日志参数""" - - -class UpdateOperaLogParam(OperaLogSchemaBase): - """更新操作日志参数""" - - -class DeleteOperaLogParam(SchemaBase): - """删除操作日志参数""" - - pks: list[int] = Field(description='操作日志 ID 列表') - - -class GetOperaLogDetail(OperaLogSchemaBase): - """操作日志详情""" - - model_config = ConfigDict(from_attributes=True) - - id: int = Field(description='日志 ID') - created_time: datetime = Field(description='创建时间') diff --git a/backend/app/admin/schema/user.py b/backend/app/admin/schema/user.py index b812a1c..eaa42ab 100644 --- a/backend/app/admin/schema/user.py +++ b/backend/app/admin/schema/user.py @@ -29,15 +29,6 @@ class AddUserParam(AuthSchemaBase): phone: CustomPhoneNumber | None = Field(None, description='手机号码') -class AddOAuth2UserParam(AuthSchemaBase): - """添加 OAuth2 用户参数""" - - password: str | None = Field(None, description='密码') - nickname: str | None = Field(None, description='昵称') - email: CustomEmailStr | None = Field(None, description='邮箱') - avatar: Annotated[HttpUrl, PlainSerializer(ser_string)] | None = Field(None, description='头像地址') - - class ResetPasswordParam(SchemaBase): """重置密码参数""" @@ -49,7 +40,6 @@ class ResetPasswordParam(SchemaBase): class UserInfoSchemaBase(SchemaBase): """用户信息基础模型""" - dept_id: int | None = Field(None, description='部门 ID') username: str = Field(description='用户名') nickname: str = Field(description='昵称') avatar: Annotated[HttpUrl, PlainSerializer(ser_string)] | None = Field(None, description='头像地址') @@ -66,7 +56,6 @@ class GetUserInfoDetail(UserInfoSchemaBase): model_config = ConfigDict(from_attributes=True) - dept_id: int | None = Field(None, description='部门 ID') id: int = Field(description='用户 ID') uuid: str = Field(description='用户 UUID') status: StatusType = Field(description='状态') @@ -75,15 +64,3 @@ class GetUserInfoDetail(UserInfoSchemaBase): is_multi_login: bool = Field(description='是否允许多端登录') join_time: datetime = Field(description='加入时间') last_login_time: datetime | None = Field(None, description='最后登录时间') - - -class GetUserInfoWithRelationDetail(GetUserInfoDetail): - """用户信息关联详情""" - - model_config = ConfigDict(from_attributes=True) - - -class GetCurrentUserInfoWithRelationDetail(GetUserInfoWithRelationDetail): - """当前用户信息关联详情""" - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/app/admin/schema/user_password_history.py b/backend/app/admin/schema/user_password_history.py new file mode 100644 index 0000000..e0ed890 --- /dev/null +++ b/backend/app/admin/schema/user_password_history.py @@ -0,0 +1,14 @@ +from pydantic import Field + +from backend.common.schema import SchemaBase + + +class UserPasswordHistoryBase(SchemaBase): + """用户历史密码记录基础模型""" + + user_id: int = Field(description='用户 ID') + password: str = Field(description='历史密码') + + +class CreateUserPasswordHistoryParam(UserPasswordHistoryBase): + """创建用户历史密码记录""" diff --git a/backend/app/admin/service/auth_service.py b/backend/app/admin/service/auth_service.py index 8f0aafb..d9a7a00 100644 --- a/backend/app/admin/service/auth_service.py +++ b/backend/app/admin/service/auth_service.py @@ -1,15 +1,14 @@ from fastapi import Request, Response from fastapi.security import HTTPBasicCredentials from sqlalchemy.ext.asyncio import AsyncSession -from starlette.background import BackgroundTask, BackgroundTasks from backend.app.admin.crud.crud_user import user_dao from backend.app.admin.model import User from backend.app.admin.schema.token import GetLoginToken, GetNewToken from backend.app.admin.schema.user import AuthLoginParam -from backend.app.admin.service.login_log_service import login_log_service +from backend.app.admin.service.user_password_history_service import password_security_service +from backend.app.admin.utils.password_security import password_verify from backend.common.context import ctx -from backend.common.enums import LoginLogStatusType from backend.common.exception import errors from backend.common.i18n import t from backend.common.log import log @@ -22,7 +21,6 @@ jwt_decode, ) from backend.core.conf import settings -from backend.database.db import uuid4_str from backend.database.redis import redis_client from backend.utils.dynamic_config import load_login_config from backend.utils.timezone import timezone @@ -45,7 +43,19 @@ async def user_verify(db: AsyncSession, username: str, password: str) -> tuple[U if not user: raise errors.NotFoundError(msg='用户名或密码有误') - return user, -1 + await password_security_service.check_status(user.id, user.status) + + if user.password is None or not password_verify(password, user.password): + await password_security_service.handle_login_failure(db, user.id) + raise errors.AuthorizationError(msg='用户名或密码有误') + + days_remaining = await password_security_service.check_password_expiry_status( + db, user.last_password_changed_time + ) + + await password_security_service.handle_login_success(user.id) + + return user, days_remaining async def swagger_login(self, *, db: AsyncSession, obj: HTTPBasicCredentials) -> tuple[str, User]: """ @@ -71,7 +81,6 @@ async def login( db: AsyncSession, response: Response, obj: AuthLoginParam, - background_tasks: BackgroundTasks, ) -> GetLoginToken: """ 用户登录 @@ -79,13 +88,9 @@ async def login( :param db: 数据库会话 :param response: 响应对象 :param obj: 登录参数 - :param background_tasks: 后台任务 :return: """ - user = None try: - user, days_remaining = await self.user_verify(db, obj.username, obj.password) - await load_login_config(db) if settings.LOGIN_CAPTCHA_ENABLED: if not obj.uuid or not obj.captcha: @@ -97,6 +102,7 @@ async def login( raise errors.CustomError(error=CustomErrorCode.CAPTCHA_ERROR) await redis_client.delete(f'{settings.LOGIN_CAPTCHA_REDIS_PREFIX}:{obj.uuid}') + user, days_remaining = await self.user_verify(db, obj.username, obj.password) await user_dao.update_login_time(db, obj.username) await db.refresh(user) access_token_data = await create_access_token( @@ -127,29 +133,11 @@ async def login( log.error('登陆错误: 用户名不存在') raise errors.NotFoundError(msg=e.msg) except (errors.RequestError, errors.CustomError) as e: - if not user: - log.error('登陆错误: 用户密码有误') - task = BackgroundTask( - login_log_service.create, - user_uuid=user.uuid if user else uuid4_str(), - username=obj.username, - login_time=timezone.now(), - status=LoginLogStatusType.fail.value, - msg=e.msg, - ) - raise errors.RequestError(code=e.code, msg=e.msg, background=task) + raise errors.RequestError(code=e.code, msg=e.msg) except Exception as e: log.error(f'登陆错误: {e}') raise else: - background_tasks.add_task( - login_log_service.create, - user_uuid=user.uuid, - username=obj.username, - login_time=timezone.now(), - status=LoginLogStatusType.success.value, - msg=t('success.login.success'), - ) data = GetLoginToken( access_token=access_token_data.access_token, access_token_expire_time=access_token_data.access_token_expire_time, diff --git a/backend/app/admin/service/login_log_service.py b/backend/app/admin/service/login_log_service.py deleted file mode 100644 index 5aad1c6..0000000 --- a/backend/app/admin/service/login_log_service.py +++ /dev/null @@ -1,90 +0,0 @@ -from datetime import datetime -from typing import Any - -from sqlalchemy.ext.asyncio import AsyncSession - -from backend.app.admin.crud.crud_login_log import login_log_dao -from backend.app.admin.schema.login_log import CreateLoginLogParam, DeleteLoginLogParam -from backend.common.context import ctx -from backend.common.log import log -from backend.common.pagination import paging_data -from backend.database.db import async_db_session - - -class LoginLogService: - """登录日志服务类""" - - @staticmethod - async def get_list(*, db: AsyncSession, username: str | None, status: int | None, ip: str | None) -> dict[str, Any]: - """ - 获取登录日志列表 - - :param db: 数据库会话 - :param username: 用户名 - :param status: 状态 - :param ip: IP 地址 - :return: - """ - log_select = await login_log_dao.get_select(username=username, status=status, ip=ip) - return await paging_data(db, log_select) - - @staticmethod - async def create( - *, - user_uuid: str, - username: str, - login_time: datetime, - status: int, - msg: str, - ) -> None: - """ - 创建登录日志 - - :param user_uuid: 用户 UUID - :param username: 用户名 - :param login_time: 登录时间 - :param status: 状态 - :param msg: 消息 - :return: - """ - try: - obj = CreateLoginLogParam( - user_uuid=user_uuid, - username=username, - status=status, - ip=ctx.ip, - country=ctx.country, - region=ctx.region, - city=ctx.city, - user_agent=ctx.user_agent, - browser=ctx.browser, - os=ctx.os, - device=ctx.device, - msg=msg, - login_time=login_time, - ) - # 为后台任务创建独立数据库会话 - async with async_db_session.begin() as db: - await login_log_dao.create(db, obj) - except Exception as e: - log.error(f'登录日志创建失败: {e}') - - @staticmethod - async def delete(*, db: AsyncSession, obj: DeleteLoginLogParam) -> int: - """ - 批量删除登录日志 - - :param db: 数据库会话 - :param obj: 日志 ID 列表 - :return: - """ - count = await login_log_dao.delete(db, obj.pks) - return count - - @staticmethod - async def delete_all(*, db: AsyncSession) -> None: - """清空所有登录日志""" - await login_log_dao.delete_all(db) - - -login_log_service: LoginLogService = LoginLogService() diff --git a/backend/app/admin/service/opera_log_service.py b/backend/app/admin/service/opera_log_service.py deleted file mode 100644 index af92be3..0000000 --- a/backend/app/admin/service/opera_log_service.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Any - -from sqlalchemy.ext.asyncio import AsyncSession - -from backend.app.admin.crud.crud_opera_log import opera_log_dao -from backend.app.admin.schema.opera_log import CreateOperaLogParam, DeleteOperaLogParam -from backend.common.pagination import paging_data - - -class OperaLogService: - """操作日志服务类""" - - @staticmethod - async def get_list(*, db: AsyncSession, username: str | None, status: int | None, ip: str | None) -> dict[str, Any]: - """ - 获取操作日志列表 - - :param db: 数据库会话 - :param username: 用户名 - :param status: 状态 - :param ip: IP 地址 - :return: - """ - log_select = await opera_log_dao.get_select(username=username, status=status, ip=ip) - return await paging_data(db, log_select) - - @staticmethod - async def create(*, db: AsyncSession, obj: CreateOperaLogParam) -> None: - """ - 创建操作日志 - - :param db: 数据库会话 - :param obj: 操作日志创建参数 - :return: - """ - await opera_log_dao.create(db, obj) - - @staticmethod - async def bulk_create(*, db: AsyncSession, objs: list[CreateOperaLogParam]) -> None: - """ - 批量创建操作日志 - - :param db: 数据库会话 - :param objs: 操作日志创建参数列表 - :return: - """ - await opera_log_dao.bulk_create(db, objs) - - @staticmethod - async def delete(*, db: AsyncSession, obj: DeleteOperaLogParam) -> int: - """ - 批量删除操作日志 - - :param db: 数据库会话 - :param obj: 日志 ID 列表 - :return: - """ - count = await opera_log_dao.delete(db, obj.pks) - return count - - @staticmethod - async def delete_all(*, db: AsyncSession) -> None: - """ - 清空所有操作日志 - - :param db: 数据库会话 - :return: - """ - await opera_log_dao.delete_all(db) - - -opera_log_service: OperaLogService = OperaLogService() diff --git a/backend/app/admin/service/plugin_service.py b/backend/app/admin/service/plugin_service.py new file mode 100644 index 0000000..0ede945 --- /dev/null +++ b/backend/app/admin/service/plugin_service.py @@ -0,0 +1,119 @@ +import io +import json + +from typing import Any + +import anyio + +from fastapi import UploadFile +from starlette.concurrency import run_in_threadpool + +from backend.common.enums import PluginType, StatusType +from backend.common.exception import errors +from backend.core.conf import settings +from backend.core.path_conf import PLUGIN_DIR +from backend.database.redis import redis_client +from backend.plugin.installer import install_git_plugin, install_zip_plugin, remove_plugin, zip_plugin +from backend.plugin.requirements import uninstall_requirements_async +from backend.utils.timezone import timezone + + +class PluginService: + """插件服务类""" + + @staticmethod + async def get_all() -> list[dict[str, Any]]: + """获取所有插件""" + + keys = [key async for key in redis_client.scan_iter(f'{settings.PLUGIN_REDIS_PREFIX}:*')] + + result = [json.loads(info) for info in await redis_client.mget(*keys)] + + return result + + @staticmethod + async def changed() -> str | None: + """检查插件是否发生变更""" + return await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:changed') + + @staticmethod + async def install(*, type: PluginType, file: UploadFile | None = None, repo_url: str | None = None) -> str: + """ + 安装插件 + + :param type: 插件类型 + :param file: 插件 zip 压缩包 + :param repo_url: git 仓库地址 + :return: + """ + if settings.ENVIRONMENT != 'dev': + raise errors.RequestError(msg='禁止在非开发环境下安装插件') + if type == PluginType.zip: + if not file: + raise errors.RequestError(msg='ZIP 压缩包不能为空') + return await install_zip_plugin(file) + if not repo_url: + raise errors.RequestError(msg='Git 仓库地址不能为空') + return await install_git_plugin(repo_url) + + @staticmethod + async def uninstall(*, plugin: str) -> None: + """ + 卸载插件 + + :param plugin: 插件名称 + :return: + """ + if settings.ENVIRONMENT != 'dev': + raise errors.RequestError(msg='禁止在非开发环境下卸载插件') + plugin_dir = anyio.Path(PLUGIN_DIR / plugin) + if not await plugin_dir.exists(): + raise errors.NotFoundError(msg='插件不存在') + await uninstall_requirements_async(plugin) + backup_file = PLUGIN_DIR / f'{plugin}.{timezone.now().strftime("%Y%m%d%H%M%S")}.backup.zip' + await run_in_threadpool(zip_plugin, plugin_dir, backup_file) + await run_in_threadpool(remove_plugin, plugin_dir) + await redis_client.delete(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}') + await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'true') + + @staticmethod + async def update_status(*, plugin: str) -> None: + """ + 更新插件状态 + + :param plugin: 插件名称 + :return: + """ + plugin_info = await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}') + if not plugin_info: + raise errors.NotFoundError(msg='插件不存在') + plugin_info = json.loads(plugin_info) + + # 更新持久缓存状态 + new_status = ( + str(StatusType.enable.value) + if plugin_info['plugin']['enable'] == str(StatusType.disable.value) + else str(StatusType.disable.value) + ) + plugin_info['plugin']['enable'] = new_status + await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}', json.dumps(plugin_info, ensure_ascii=False)) + + @staticmethod + async def build(*, plugin: str) -> io.BytesIO: + """ + 打包插件为 zip 压缩包 + + :param plugin: 插件名称 + :return: + """ + plugin_dir = anyio.Path(PLUGIN_DIR / plugin) + if not await plugin_dir.exists(): + raise errors.NotFoundError(msg='插件不存在') + + bio = io.BytesIO() + await run_in_threadpool(zip_plugin, plugin_dir, bio) + bio.seek(0) + return bio + + +plugin_service: PluginService = PluginService() diff --git a/backend/app/admin/service/user_password_history_service.py b/backend/app/admin/service/user_password_history_service.py new file mode 100644 index 0000000..36314be --- /dev/null +++ b/backend/app/admin/service/user_password_history_service.py @@ -0,0 +1,126 @@ +import math + +from datetime import datetime, timedelta + +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.app.admin.crud.crud_user_password_history import user_password_history_dao +from backend.app.admin.schema.user_password_history import CreateUserPasswordHistoryParam +from backend.common.exception import errors +from backend.core.conf import settings +from backend.database.redis import redis_client +from backend.utils.dynamic_config import load_user_security_config +from backend.utils.timezone import timezone + + +class UserPasswordHistoryService: + """用户密码历史服务类""" + + @staticmethod + async def check_status(user_id: int, user_status: int) -> None: + """ + 检查用户状态 + + :param user_id: 用户 ID + :param user_status: 用户状态 + :return: + """ + if not user_status: + raise errors.AuthorizationError(msg='用户已被锁定, 请联系统管理员') + + locked_until_str = await redis_client.get(f'{settings.USER_LOCK_REDIS_PREFIX}:{user_id}') + + if locked_until_str: + locked_until = timezone.from_str(locked_until_str) + now = timezone.now() + if locked_until > now: + remaining_minutes = math.ceil((locked_until - now).total_seconds() / 60) + raise errors.AuthorizationError(msg=f'账号已被锁定,请在 {remaining_minutes} 分钟后重试') + + await redis_client.delete(f'{settings.USER_LOCK_REDIS_PREFIX}:{user_id}') + await redis_client.delete(f'{settings.LOGIN_FAILURE_PREFIX}:{user_id}') + + @staticmethod + async def handle_login_failure(db: AsyncSession, user_id: int) -> None: + """ + 处理登录失败 + + :param db: 数据库会话 + :param user_id: 用户 ID + :return: + """ + await load_user_security_config(db) + + if settings.USER_LOCK_THRESHOLD == 0: + return + + failure_count = await redis_client.get(f'{settings.LOGIN_FAILURE_PREFIX}:{user_id}') + failure_count = int(failure_count) if failure_count else 0 + failure_count += 1 + await redis_client.setex( + f'{settings.LOGIN_FAILURE_PREFIX}:{user_id}', + settings.USER_LOCK_SECONDS, + str(failure_count), + ) + + if failure_count >= settings.USER_LOCK_THRESHOLD: + locked_until = timezone.now() + timedelta(seconds=settings.USER_LOCK_SECONDS) + await redis_client.setex( + f'{settings.USER_LOCK_REDIS_PREFIX}:{user_id}', + settings.USER_LOCK_SECONDS, + timezone.to_str(locked_until), + ) + raise errors.AuthorizationError(msg='登录失败次数过多,账号已被锁定') + + @staticmethod + async def check_password_expiry_status(db: AsyncSession, password_changed_time: datetime) -> int | None: + """ + 检查密码过期状态 + + :param db: 数据库会话 + :param password_changed_time: 密码修改时间 + :return: + """ + await load_user_security_config(db) + + if settings.USER_PASSWORD_EXPIRY_DAYS == 0: + return None + + if not password_changed_time: + raise errors.AuthorizationError(msg='密码已过期,请修改密码后重新登录') + + expiry_time = password_changed_time + timedelta(days=settings.USER_PASSWORD_EXPIRY_DAYS) + days_remaining = (expiry_time - timezone.now()).days + + if days_remaining < 0: + raise errors.AuthorizationError(msg='密码已过期,请修改密码后重新登录') + + if days_remaining <= settings.USER_PASSWORD_REMINDER_DAYS: + return days_remaining + + return None + + @staticmethod + async def handle_login_success(user_id: int) -> None: + """ + 处理登录成功 + + :param user_id: 用户 ID + :return: + """ + await redis_client.delete(f'{settings.USER_LOCK_REDIS_PREFIX}:{user_id}') + await redis_client.delete(f'{settings.LOGIN_FAILURE_PREFIX}:{user_id}') + + @staticmethod + async def save_password_history(db: AsyncSession, obj: CreateUserPasswordHistoryParam) -> None: + """ + 保存密码历史记录 + + :param db: 数据库会话 + :param obj: 创建密码历史记录参数 + :return: + """ + await user_password_history_dao.create(db, obj) + + +password_security_service: UserPasswordHistoryService = UserPasswordHistoryService() diff --git a/backend/app/admin/service/user_service.py b/backend/app/admin/service/user_service.py index 350b41d..6ea1841 100644 --- a/backend/app/admin/service/user_service.py +++ b/backend/app/admin/service/user_service.py @@ -10,16 +10,15 @@ ResetPasswordParam, UpdateUserParam, ) +from backend.app.admin.schema.user_password_history import CreateUserPasswordHistoryParam +from backend.app.admin.service.user_password_history_service import password_security_service from backend.app.admin.utils.password_security import password_verify, validate_new_password -from backend.common.context import ctx from backend.common.enums import UserPermissionType from backend.common.exception import errors from backend.common.pagination import paging_data -from backend.common.response.response_code import CustomErrorCode from backend.common.security.jwt import get_token, jwt_decode from backend.core.conf import settings from backend.database.redis import redis_client -from backend.utils.serializers import select_join_serialize class UserService: @@ -35,7 +34,12 @@ async def get_userinfo(*, db: AsyncSession, pk: int | None = None, username: str :param username: 用户名 :return: """ - user = await user_dao.get_join(db, user_id=pk, username=username) + if pk: + user = await user_dao.get(db, pk) + elif username: + user = await user_dao.get_by_username(db, username) + else: + user = None if not user: raise errors.NotFoundError(msg='用户不存在') return user @@ -53,10 +57,6 @@ async def get_list(*, db: AsyncSession, username: str, phone: str, status: int) """ user_select = await user_dao.get_select(username=username, phone=phone, status=status) data = await paging_data(db, user_select) - if data['items']: - serialized_items = select_join_serialize(data['items']) - # 确保返回的是列表,即使只有一个元素 - data['items'] = [serialized_items] if not isinstance(serialized_items, list) else serialized_items return data @staticmethod @@ -85,7 +85,7 @@ async def update(*, db: AsyncSession, pk: int, obj: UpdateUserParam) -> int: :param obj: 用户更新参数 :return: """ - user = await user_dao.get_join(db, user_id=pk) + user = await user_dao.get(db, pk) if not user: raise errors.NotFoundError(msg='用户不存在') if obj.username != user.username and await user_dao.get_by_username(db, obj.username): @@ -137,7 +137,6 @@ async def update_permission(*, db: AsyncSession, request: Request, pk: int, type token = get_token(request) token_payload = jwt_decode(token) if pk == user.id: - # 系统管理员修改自身时,除当前 token 外,其他 token 失效 if not new_multi_login: key_prefix = f'{settings.TOKEN_REDIS_PREFIX}:{user.id}' await redis_client.delete_prefix( @@ -145,7 +144,6 @@ async def update_permission(*, db: AsyncSession, request: Request, pk: int, type exclude=f'{key_prefix}:{token_payload.session_uuid}', ) else: - # 系统管理员修改他人时,他人 token 全部失效 if not new_multi_login: key_prefix = f'{settings.TOKEN_REDIS_PREFIX}:{user.id}' await redis_client.delete_prefix(key_prefix) @@ -169,9 +167,11 @@ async def reset_password(*, db: AsyncSession, pk: int, password: str) -> int: if not user: raise errors.NotFoundError(msg='用户不存在') - validate_new_password(password) + await validate_new_password(db, user.id, password) count = await user_dao.reset_password(db, user.id, password) + history_obj = CreateUserPasswordHistoryParam(user_id=user.id, password=user.password) + await password_security_service.save_password_history(db, history_obj) await user_dao.update_password_changed_time(db, user.id) key_prefix = [ @@ -180,7 +180,7 @@ async def reset_password(*, db: AsyncSession, pk: int, password: str) -> int: f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}', ] for prefix in key_prefix: - await redis_client.delete(prefix) + await redis_client.delete_prefix(prefix) return count @staticmethod @@ -212,22 +212,15 @@ async def update_avatar(*, db: AsyncSession, user_id: int, avatar: str) -> int: return count @staticmethod - async def update_email(*, db: AsyncSession, user_id: int, captcha: str, email: str) -> int: + async def update_email(*, db: AsyncSession, user_id: int, email: str) -> int: """ 更新当前用户邮箱 :param db: 数据库会话 :param user_id: 用户 ID - :param captcha: 邮箱验证码 :param email: 邮箱 :return: """ - captcha_code = await redis_client.get(f'{settings.EMAIL_CAPTCHA_REDIS_PREFIX}:{ctx.ip}') - if not captcha_code: - raise errors.RequestError(msg='验证码已失效,请重新获取') - if captcha != captcha_code: - raise errors.CustomError(error=CustomErrorCode.CAPTCHA_ERROR) - await redis_client.delete(f'{settings.EMAIL_CAPTCHA_REDIS_PREFIX}:{ctx.ip}') count = await user_dao.update_email(db, user_id, email) await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}') return count @@ -250,9 +243,11 @@ async def update_password(*, db: AsyncSession, user_id: int, obj: ResetPasswordP if obj.new_password != obj.confirm_password: raise errors.RequestError(msg='两次密码输入不一致') - validate_new_password(obj.new_password) + await validate_new_password(db, user_id, obj.new_password) count = await user_dao.reset_password(db, user_id, obj.new_password) + history_obj = CreateUserPasswordHistoryParam(user_id=user.id, password=user.password) + await password_security_service.save_password_history(db, history_obj) await user_dao.update_password_changed_time(db, user.id) key_prefix = [ @@ -280,6 +275,7 @@ async def delete(*, db: AsyncSession, pk: int) -> int: key_prefix = [ f'{settings.TOKEN_REDIS_PREFIX}:{user.id}', f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}', + f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}', ] for key in key_prefix: await redis_client.delete_prefix(key) diff --git a/backend/app/admin/utils/cache.py b/backend/app/admin/utils/cache.py new file mode 100644 index 0000000..7f34fd7 --- /dev/null +++ b/backend/app/admin/utils/cache.py @@ -0,0 +1,22 @@ +from collections.abc import Sequence + +from backend.core.conf import settings +from backend.database.redis import redis_client + + +class UserCacheManager: + """用户缓存管理""" + + @staticmethod + async def clear(user_ids: Sequence[int]) -> None: + """ + 清理用户缓存 + + :param user_ids: 用户 ID 列表 + :return: + """ + if user_ids: + await redis_client.delete(*[f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}' for user_id in user_ids]) + + +user_cache_manager: UserCacheManager = UserCacheManager() diff --git a/backend/app/admin/utils/password_security.py b/backend/app/admin/utils/password_security.py index 35385a7..f7fbc28 100644 --- a/backend/app/admin/utils/password_security.py +++ b/backend/app/admin/utils/password_security.py @@ -1,8 +1,11 @@ from pwdlib import PasswordHash from pwdlib.hashers.bcrypt import BcryptHasher +from sqlalchemy.ext.asyncio import AsyncSession +from backend.app.admin.crud.crud_user_password_history import user_password_history_dao from backend.common.exception import errors from backend.core.conf import settings +from backend.utils.dynamic_config import load_user_security_config from backend.utils.pattern_validate import is_has_letter, is_has_number, is_has_special_char password_hash = PasswordHash((BcryptHasher(),)) @@ -30,13 +33,17 @@ def password_verify(plain_password: str, hashed_password: str) -> bool: return password_hash.verify(plain_password, hashed_password) -def validate_new_password(new_password: str) -> None: +async def validate_new_password(db: AsyncSession, user_id: int, new_password: str) -> None: """ 验证新密码 + :param db: 数据库会话 + :param user_id: 用户ID :param new_password: 新密码 :return: """ + await load_user_security_config(db) + if len(new_password) < settings.USER_PASSWORD_MIN_LENGTH: raise errors.RequestError(msg=f'密码长度不能少于 {settings.USER_PASSWORD_MIN_LENGTH} 个字符') @@ -51,3 +58,11 @@ def validate_new_password(new_password: str) -> None: if settings.USER_PASSWORD_REQUIRE_SPECIAL_CHAR and not is_has_special_char(new_password): raise errors.RequestError(msg='密码必须包含特殊字符(如:!@#$%)') + + password_history = await user_password_history_dao.get_by_user_id(db, user_id) + + for hist in password_history[: settings.USER_PASSWORD_HISTORY_CHECK_COUNT]: + if password_verify(new_password, hist.password): + raise errors.RequestError( + msg=f'新密码不能与最近 {settings.USER_PASSWORD_HISTORY_CHECK_COUNT} 次使用的密码相同' + ) diff --git a/backend/cli.py b/backend/cli.py index b51f377..38cc6de 100644 --- a/backend/cli.py +++ b/backend/cli.py @@ -1,6 +1,7 @@ import asyncio import re import secrets +import subprocess import sys from dataclasses import dataclass @@ -21,10 +22,10 @@ from backend import __version__ from backend.common.enums import DataBaseType, PrimaryKeyType -from backend.common.exception.errors import BaseExceptionError from backend.common.model import MappedBase from backend.core.conf import settings from backend.core.path_conf import ( + BASE_PATH, ENV_EXAMPLE_FILE_PATH, ENV_FILE_PATH, MYSQL_SCRIPT_DIR, @@ -39,11 +40,10 @@ ) from backend.database.redis import RedisCli, redis_client from backend.plugin.core import get_plugin_sql, get_plugins -from backend.plugin.installer import install_git_plugin, install_zip_plugin from backend.utils.console import console from backend.utils.sql_parser import parse_sql_script -output_help = '\n更多信息,尝试 "[cyan]--help[/]"' +output_help = "\n更多信息,尝试 '[cyan]--help[/]'" class CustomReloadFilter(PythonFilter): @@ -59,29 +59,30 @@ def __call__(self, change: Change, path: str) -> bool: def setup_env_file() -> bool: + """交互式配置并生成 .env 环境变量文件""" if not ENV_EXAMPLE_FILE_PATH.exists(): - console.print('.env.example 文件不存在', style='red') + console.caution('.env.example 文件不存在') return False try: env_content = Path(ENV_EXAMPLE_FILE_PATH).read_text(encoding='utf-8') - console.print('配置数据库连接信息...', style='white') + console.note('配置数据库连接信息...') db_type = Prompt.ask('数据库类型', choices=['mysql', 'postgresql'], default='postgresql') db_host = Prompt.ask('数据库主机', default='127.0.0.1') db_port = Prompt.ask('数据库端口', default='5432' if db_type == 'postgresql' else '3306') db_user = Prompt.ask('数据库用户名', default='postgres' if db_type == 'postgresql' else 'root') db_password = Prompt.ask('数据库密码', password=True, default='123456') - console.print('配置 Redis 连接信息...', style='white') + console.note('配置 Redis 连接信息...') redis_host = Prompt.ask('Redis 主机', default='127.0.0.1') redis_port = Prompt.ask('Redis 端口', default='6379') redis_password = Prompt.ask('Redis 密码(留空表示无密码)', password=True, default='') redis_db = Prompt.ask('Redis 数据库编号', default='0') - console.print('生成 Token 密钥...', style='white') + console.info('生成 Token 密钥...') token_secret = secrets.token_urlsafe(32) - console.print('写入 .env 文件...', style='white') + console.info('写入 .env 文件...') env_content = env_content.replace("DATABASE_TYPE='postgresql'", f"DATABASE_TYPE='{db_type}'") settings.DATABASE_TYPE = db_type env_content = env_content.replace("DATABASE_HOST='127.0.0.1'", f"DATABASE_HOST='{db_host}'") @@ -104,15 +105,16 @@ def setup_env_file() -> bool: settings.TOKEN_SECRET_KEY = token_secret Path(ENV_FILE_PATH).write_text(env_content, encoding='utf-8') - console.print('.env 文件创建成功', style='green') + console.tip('.env 文件创建成功') except Exception as e: - console.print(f'.env 文件创建失败: {e}', style='red') + console.caution(f'.env 文件创建失败: {e}') return False else: return True async def create_database(conn: AsyncConnection) -> bool: + """创建或重建数据库""" try: terminate_sql = None if DataBaseType.mysql == settings.DATABASE_TYPE: @@ -133,20 +135,35 @@ async def create_database(conn: AsyncConnection) -> bool: result = await conn.execute(text(check_sql)) exists = result.fetchone() is not None - console.print(f'重建 {settings.DATABASE_SCHEMA} 数据库...', style='white') + console.note(f'重建 {settings.DATABASE_SCHEMA} 数据库...') if exists: if terminate_sql: await conn.execute(text(terminate_sql)) await conn.execute(text(drop_sql)) await conn.execute(text(create_sql)) - console.print('数据库创建成功', style='green') + console.tip('数据库创建成功') except Exception as e: - console.print(f'数据库创建失败: {e}', style='red') + console.caution(f'数据库创建失败: {e}') return False else: return True +def _build_db_config_panel_content() -> Text: + """构建数据库配置面板内容""" + panel_content = Text() + panel_content.append('【数据库配置】', style='bold green') + panel_content.append('\n\n • 类型: ') + panel_content.append(f'{settings.DATABASE_TYPE}', style='yellow') + panel_content.append('\n • 主机:') + panel_content.append(f'{settings.DATABASE_HOST}:{settings.DATABASE_PORT}', style='yellow') + panel_content.append('\n • 数据库:') + panel_content.append(f'{settings.DATABASE_SCHEMA}', style='yellow') + panel_content.append('\n • 主键模式:') + panel_content.append(f'{settings.DATABASE_PK_MODE}', style='yellow') + return panel_content + + async def auto_init() -> None: """自动化初始化流程""" console.print('\n[bold cyan]步骤 1/3:[/] 配置环境变量', style='bold') @@ -161,16 +178,7 @@ async def auto_init() -> None: raise cappa.Exit('.env 文件配置失败', code=1) console.print('\n[bold cyan]步骤 2/3:[/] 数据库创建', style='bold') - panel_content = Text() - panel_content.append('【数据库配置】', style='bold green') - panel_content.append('\n\n • 类型: ') - panel_content.append(f'{settings.DATABASE_TYPE}', style='yellow') - panel_content.append('\n • 主机:') - panel_content.append(f'{settings.DATABASE_HOST}:{settings.DATABASE_PORT}', style='yellow') - panel_content.append('\n • 数据库:') - panel_content.append(f'{settings.DATABASE_SCHEMA}', style='yellow') - panel_content.append('\n • 主键模式:') - panel_content.append(f'{settings.DATABASE_PK_MODE}', style='yellow') + panel_content = _build_db_config_panel_content() console.print(Panel(panel_content, title=f'fba (v{__version__}) - 数据库', border_style='cyan', padding=(1, 2))) ok = Prompt.ask('即将[red]新建/重建数据库[/red],确认继续吗?', choices=['y', 'n'], default='n') @@ -182,7 +190,7 @@ async def auto_init() -> None: if not await create_database(conn): raise cappa.Exit('数据库创建失败', code=1) else: - console.print('已取消数据库操作', style='yellow') + console.warning('已取消数据库操作') console.print('\n[bold cyan]步骤 3/3:[/] 初始化数据库表和数据', style='bold') async_init_engine = create_database_async_engine(create_database_url()) @@ -199,16 +207,8 @@ async def auto_init() -> None: async def init(db: AsyncSession, redis: RedisCli) -> None: - panel_content = Text() - panel_content.append('【数据库配置】', style='bold green') - panel_content.append('\n\n • 类型: ') - panel_content.append(f'{settings.DATABASE_TYPE}', style='yellow') - panel_content.append('\n • 主机:') - panel_content.append(f'{settings.DATABASE_HOST}:{settings.DATABASE_PORT}', style='yellow') - panel_content.append('\n • 数据库:') - panel_content.append(f'{settings.DATABASE_SCHEMA}', style='yellow') - panel_content.append('\n • 主键模式:') - panel_content.append(f'{settings.DATABASE_PK_MODE}', style='yellow') + """交互式初始化数据库表结构和数据""" + panel_content = _build_db_config_panel_content() pk_details = panel_content.from_markup( '[link=https://fastapi-practices.github.io/fastapi_best_architecture_docs/backend/reference/pk.html](了解详情)[/]' ) @@ -232,9 +232,8 @@ async def init(db: AsyncSession, redis: RedisCli) -> None: ) if ok.lower() == 'y': - console.print('开始初始化...', style='white') try: - console.print('清理 Redis 缓存', style='white') + console.note('清理 Redis 缓存') for prefix in [ settings.JWT_USER_REDIS_PREFIX, settings.TOKEN_EXTRA_INFO_REDIS_PREFIX, @@ -243,26 +242,27 @@ async def init(db: AsyncSession, redis: RedisCli) -> None: ]: await redis.delete_prefix(prefix) - console.print('重建数据库表', style='white') + console.note('重建数据库表') conn = await db.connection() await conn.run_sync(MappedBase.metadata.drop_all) await conn.run_sync(MappedBase.metadata.create_all) - console.print('执行 SQL 脚本', style='white') + console.note('执行 SQL 脚本') sql_scripts = await get_sql_scripts() for sql_script in sql_scripts: - console.print(f'正在执行:{sql_script}', style='white') + console.note(f'正在执行:{sql_script}') await execute_sql_scripts(db, sql_script, is_init=True) - console.print('初始化成功', style='green') + console.tip('初始化成功') console.print('\n快试试 [bold cyan]fba run[/bold cyan] 启动服务吧~') except Exception as e: raise cappa.Exit(f'初始化失败:{e}', code=1) else: - console.print('已取消初始化操作', style='yellow') + console.warning('已取消初始化操作') def run(host: str, port: int, reload: bool, workers: int) -> None: # noqa: FBT001 + """启动 API 服务""" url = f'http://{host}:{port}' docs_url = url + settings.FASTAPI_DOCS_URL redoc_url = url + settings.FASTAPI_REDOC_URL @@ -306,43 +306,8 @@ def run(host: str, port: int, reload: bool, workers: int) -> None: # noqa: FBT0 ).serve() -async def install_plugin( - path: str, - repo_url: str, - no_sql: bool, # noqa: FBT001 - db_type: DataBaseType, - pk_type: PrimaryKeyType, -) -> None: - if settings.ENVIRONMENT != 'dev': - raise cappa.Exit('插件安装仅在开发环境可用', code=1) - - if not path and not repo_url: - raise cappa.Exit('path 或 repo_url 必须指定其中一项', code=1) - if path and repo_url: - raise cappa.Exit('path 和 repo_url 不能同时指定', code=1) - - plugin_name = None - console.print('开始安装插件...', style='bold cyan') - - try: - if path: - plugin_name = await install_zip_plugin(file=path) - if repo_url: - plugin_name = await install_git_plugin(repo_url=repo_url) - - console.print(f'插件 {plugin_name} 安装成功', style='bold green') - - sql_file = await get_plugin_sql(plugin_name, db_type, pk_type) - if sql_file and not no_sql: - console.print('开始自动执行插件 SQL 脚本...', style='bold cyan') - async with async_db_session.begin() as db: - await execute_sql_scripts(db, sql_file) - - except Exception as e: - raise cappa.Exit(e.msg if isinstance(e, BaseExceptionError) else str(e), code=1) - - async def get_sql_scripts() -> list[str]: + """获取所有待执行的 SQL 脚本路径列表""" sql_scripts = [] db_script_dir = MYSQL_SCRIPT_DIR if DataBaseType.mysql == settings.DATABASE_TYPE else POSTGRESQL_SCRIPT_DIR main_sql_file = ( @@ -365,6 +330,7 @@ async def get_sql_scripts() -> list[str]: async def execute_sql_scripts(db: AsyncSession, sql_scripts: str, *, is_init: bool = False) -> None: + """解析并执行 SQL 脚本""" try: stmts = await parse_sql_script(sql_scripts) for stmt in stmts: @@ -373,7 +339,15 @@ async def execute_sql_scripts(db: AsyncSession, sql_scripts: str, *, is_init: bo raise cappa.Exit(f'SQL 脚本执行失败:{e}', code=1) if not is_init: - console.print('SQL 脚本已执行完成', style='bold green') + console.tip('SQL 脚本已执行完成') + + +def run_alembic(*args: str) -> None: + """执行 alembic 命令""" + try: + subprocess.run(['alembic', *args], cwd=BASE_PATH.parent, check=True) + except subprocess.CalledProcessError as e: + raise cappa.Exit('Alembic 命令执行失败', code=e.returncode) @cappa.command(help='初始化 fba 项目', default_long=True) @@ -420,32 +394,121 @@ def __call__(self) -> None: run(host=self.host, port=self.port, reload=self.no_reload, workers=self.workers) -@cappa.command(help='新增插件', default_long=True) +@cappa.command(help='格式化代码') @dataclass -class Add: - path: Annotated[ - str | None, - cappa.Arg(help='ZIP 插件的本地完整路径'), +class Format: + def __call__(self) -> None: + try: + subprocess.run(['prek', 'run', '--all-files'], cwd=BASE_PATH.parent, check=False) + except FileNotFoundError: + raise cappa.Exit('prek 未安装,请先安装项目依赖', code=1) + except KeyboardInterrupt: + pass + + +@cappa.command(help='生成数据库迁移文件', default_long=True) +@dataclass +class Revision: + autogenerate: Annotated[ + bool, + cappa.Arg(default=True, help='自动检测模型变更并生成迁移脚本'), ] - repo_url: Annotated[ - str | None, - cappa.Arg(help='Git 插件的仓库地址'), + message: Annotated[ + str, + cappa.Arg(short='-m', default='', help='迁移文件的描述信息'), ] - no_sql: Annotated[ + + def __call__(self) -> None: + args = ['revision'] + if self.autogenerate: + args.append('--autogenerate') + if self.message: + args.extend(['-m', self.message]) + run_alembic(*args) + console.tip('迁移文件生成成功') + + +@cappa.command(help='升级数据库到指定版本', default_long=True) +@dataclass +class Upgrade: + revision: Annotated[ + str, + cappa.Arg(default='head', help='目标版本,默认为最新版本'), + ] + + def __call__(self) -> None: + run_alembic('upgrade', self.revision) + console.tip(f'数据库已升级到: {self.revision}') + + +@cappa.command(help='降级数据库到指定版本', default_long=True) +@dataclass +class Downgrade: + revision: Annotated[ + str, + cappa.Arg(default='-1', help='目标版本,默认回退一个版本'), + ] + + def __call__(self) -> None: + run_alembic('downgrade', self.revision) + console.tip(f'数据库已降级到: {self.revision}') + + +@cappa.command(help='显示数据库当前迁移版本') +@dataclass +class Current: + verbose: Annotated[ bool, - cappa.Arg(default=False, help='禁用插件 SQL 脚本自动执行'), + cappa.Arg(short='-v', default=False, help='显示详细信息'), ] - db_type: Annotated[ - DataBaseType, - cappa.Arg(default='postgresql', help='执行插件 SQL 脚本的数据库类型'), + + def __call__(self) -> None: + args = ['current'] + if self.verbose: + args.append('-v') + run_alembic(*args) + + +@cappa.command(help='显示迁移历史记录', default_long=True) +@dataclass +class History: + verbose: Annotated[ + bool, + cappa.Arg(short='-v', default=False, help='显示详细信息'), ] - pk_type: Annotated[ - PrimaryKeyType, - cappa.Arg(default='autoincrement', help='执行插件 SQL 脚本数据库主键类型'), + range: Annotated[ + str, + cappa.Arg(short='-r', default='', help='显示指定范围的历史,例如 -r base:head'), ] - async def __call__(self) -> None: - await install_plugin(self.path, self.repo_url, self.no_sql, self.db_type, self.pk_type) + def __call__(self) -> None: + args = ['history'] + if self.verbose: + args.append('-v') + if self.range: + args.extend(['-r', self.range]) + run_alembic(*args) + + +@cappa.command(help='显示所有头版本') +@dataclass +class Heads: + verbose: Annotated[ + bool, + cappa.Arg(short='-v', default=False, help='显示详细信息'), + ] + + def __call__(self) -> None: + args = ['heads'] + if self.verbose: + args.append('-v') + run_alembic(*args) + + +@cappa.command(help='数据库迁移管理') +@dataclass +class Alembic: + subcmd: cappa.Subcommands[Revision | Upgrade | Downgrade | Current | History | Heads] @cappa.command(help='一个高效的 fba 命令行界面', default_long=True) @@ -455,7 +518,7 @@ class FbaCli: str, cappa.Arg(value_name='PATH', default='', show_default=False, help='在事务中执行 SQL 脚本'), ] - subcmd: cappa.Subcommands[Init | Run | Add | None] = None + subcmd: cappa.Subcommands[Init | Run | Format | Alembic | None] = None async def __call__(self) -> None: if self.sql: diff --git a/backend/common/prometheus/__init__.py b/backend/common/cache/__init__.py similarity index 100% rename from backend/common/prometheus/__init__.py rename to backend/common/cache/__init__.py diff --git a/backend/common/cache/decorator.py b/backend/common/cache/decorator.py new file mode 100644 index 0000000..a383ec8 --- /dev/null +++ b/backend/common/cache/decorator.py @@ -0,0 +1,233 @@ +import functools + +from collections.abc import Callable, Sequence +from typing import Any, ParamSpec, TypeVar + +from msgspec import json + +from backend.common.cache.local import local_cache_manager +from backend.common.cache.pubsub import cache_pubsub_manager +from backend.common.context import ctx +from backend.common.exception import errors +from backend.common.log import log +from backend.core.conf import settings +from backend.database.redis import redis_client +from backend.utils.serializers import select_columns_serialize, select_list_serialize + +P = ParamSpec('P') +T = TypeVar('T') + + +def _build_cache_key( + name: str, + key: str | None, + key_builder: Callable[..., str] | None, + *args: Any, + **kwargs: Any, +) -> str: + """构建缓存 Key""" + if key: + if '.' in key: + param, field = key.split('.', 1) + value = kwargs.get(param) + if value is None: + raise errors.ServerError(msg=f'缓存键构建失败,参数 "{param}" 不存在或值为空') + + if isinstance(value, list): + raise errors.ServerError(msg='缓存键构建失败:不支持从列表中提取字段,请使用 key_builder 处理列表参数') + + if hasattr(value, field): + value = getattr(value, field) + elif isinstance(value, dict) and field in value: + value = value[field] + else: + raise errors.ServerError(msg=f'缓存键构建失败,对象中不存在字段 "{field}"') + else: + value = kwargs.get(key) + if value is None: + raise errors.ServerError(msg=f'缓存键构建失败,参数 "{key}" 不存在或值为空') + + return f'{name}:{value}' + + if key_builder: + return f'{name}:{key_builder(*args, **kwargs)}' + + return name + + +def _serialize_result(result: Any) -> bytes: + """ + 序列化缓存结果 + + :param result: 需要进行序列化的结果 + :return: + """ + # SQLAlchemy 查询表 + if hasattr(result, '__table__'): + return json.encode(select_columns_serialize(result)) + + # SQLAlchemy 查询列表 + if ( + isinstance(result, Sequence) + and not isinstance(result, (str, bytes)) + and len(result) > 0 + and hasattr(result[0], '__table__') + ): + return json.encode(select_list_serialize(result)) + + # 基本类型 + return json.encode(result) + + +def _deserialize_result(value: bytes) -> Any: + """ + 反序列化缓存结果 + + :param value: 缓存结果 + :return: + """ + try: + return json.decode(value) + except Exception: + return value + + +def user_key_builder() -> str: + """基于当前用户 ID 生成缓存 Key""" + user_id = ctx.user_id + if user_id is None: + raise errors.ServerError(msg='用户缓存键构建失败') + return str(user_id) + + +def cached( # noqa: C901 + name: str, + *, + key: str | None = None, + key_builder: Callable[..., str] | None = None, +) -> Callable[[Callable[P, T]], Callable[P, T]]: + """ + 缓存装饰器 + + :param name: 缓存名称(通常为缓存 Key 前缀) + :param key: 从方法参数中获取指定参数名的值作为缓存 Key,与 key_builder 互斥 + :param key_builder: 自定义 Key 生成函数,与 key 互斥 + :return: + """ + if key is not None and key_builder is not None: + raise errors.ServerError(msg='缓存 key 和 key_builder 不能同时使用') + + def decorator(func: Callable[P, T]) -> Callable[P, T]: # noqa: C901 + @functools.wraps(func) + async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + cache_key = _build_cache_key(name, key, key_builder, *args, **kwargs) + + # L1: 本地缓存 + if settings.CACHE_LOCAL_ENABLED: + local_value = local_cache_manager.get(cache_key) + if local_value is not None: + return local_value + + # L2: Redis 缓存 + try: + redis_value = await redis_client.get(cache_key) + if redis_value is not None: + result = _deserialize_result(redis_value) + # 回填 L1 + if settings.CACHE_LOCAL_ENABLED: + local_cache_manager.set(cache_key, result) + return result + except Exception as e: + log.warning(f'[Cache] GET error: {e}') + + # 缓存未命中 + result = await func(*args, **kwargs) + + if result is not None: + try: + # 回填 L1 + if settings.CACHE_LOCAL_ENABLED: + local_cache_manager.set(cache_key, result) + + # 回填 L2 + serialized_result = _serialize_result(result) + if settings.CACHE_REDIS_TTL: + await redis_client.setex(cache_key, settings.CACHE_REDIS_TTL, serialized_result) + else: + await redis_client.set(cache_key, serialized_result) + except Exception as e: + log.warning(f'[Cache] SET error: {e}') + + return result + + return wrapper + + return decorator + + +def cache_invalidate( # noqa: C901 + name: str, + *, + key: str | None = None, + key_builder: Callable[..., str] | None = None, + atomic: bool = True, +) -> Callable[[Callable[P, T]], Callable[P, T]]: + """ + 缓存失效装饰器 + + :param name: 缓存名称(通常为缓存 Key 前缀) + :param key: 从方法参数中获取指定参数名的值作为缓存 Key,与 key_builder 互斥 + :param key_builder: 自定义 Key 生成函数,与 key 互斥 + :param atomic: 是否保证缓存原子性 + :return: + """ + if key is not None and key_builder is not None: + raise errors.ServerError(msg='缓存 key 和 key_builder 不能同时使用') + + def decorator(func: Callable[P, T]) -> Callable[P, T]: + @functools.wraps(func) + async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + result = await func(*args, **kwargs) + + # 尝试失效缓存 + invalidate_success = False + invalidate_error = None + + try: + invalidate_key = _build_cache_key(name, key, key_builder, *args, **kwargs) + + # L1 缓存失效 + if settings.CACHE_LOCAL_ENABLED: + if invalidate_key == name: + local_cache_manager.delete_prefix(invalidate_key) + else: + local_cache_manager.delete(invalidate_key) + + # 广播失效消息(通知其他节点清除本地缓存) + if settings.CACHE_LOCAL_ENABLED: + if invalidate_key == name: + await cache_pubsub_manager.publish_invalidation(invalidate_key, is_delete_prefix=True) + else: + await cache_pubsub_manager.publish_invalidation(invalidate_key) + + # L2 缓存失效 + if invalidate_key == name: + await redis_client.delete_prefix(invalidate_key) + else: + await redis_client.delete(invalidate_key) + + except Exception as e: + log.error(f'[Cache] INVALIDATE error: {e}') + invalidate_error = e + else: + invalidate_success = True + + # 原子性检查 + if atomic and not invalidate_success: + raise errors.ServerError(msg='缓存失效失败,数据可能不一致', data=invalidate_error) + + return result + + return wrapper + + return decorator diff --git a/backend/common/cache/local.py b/backend/common/cache/local.py new file mode 100644 index 0000000..4292d58 --- /dev/null +++ b/backend/common/cache/local.py @@ -0,0 +1,56 @@ +from typing import Any + +import cachebox + +from backend.core.conf import settings + + +class LocalCacheManager: + """本地缓存管理器""" + + def __init__(self) -> None: + self.hot_cache: cachebox.TTLCache = cachebox.TTLCache( + settings.CACHE_LOCAL_MAXSIZE, ttl=settings.CACHE_LOCAL_TTL + ) + + def get(self, key: str) -> Any: + """获取缓存""" + try: + return self.hot_cache[key] + except KeyError: + return None + + def set(self, key: str, value: Any) -> None: + """设置缓存""" + self.hot_cache[key] = value + + def delete(self, key: str) -> bool: + """删除缓存""" + try: + del self.hot_cache[key] + except KeyError: + return False + return True + + def clear(self) -> None: + """清空缓存""" + self.hot_cache.clear() + + def delete_prefix(self, prefix: str, exclude: str | list[str] | None = None) -> None: + """ + 删除指定前缀的缓存 + + :param prefix: 要删除的键前缀 + :param exclude: 要排除的键或键列表 + :return: + """ + exclude_set = set(exclude) if isinstance(exclude, list) else {exclude} if isinstance(exclude, str) else set() + for key in list(self.hot_cache.keys()): + if key.startswith(prefix) and key not in exclude_set: + try: + del self.hot_cache[key] + except KeyError: + pass + + +local_cache_manager = LocalCacheManager() diff --git a/backend/common/cache/pubsub.py b/backend/common/cache/pubsub.py new file mode 100644 index 0000000..0f1a6e6 --- /dev/null +++ b/backend/common/cache/pubsub.py @@ -0,0 +1,112 @@ +import asyncio +import json + +from backend.common.cache.local import local_cache_manager +from backend.common.log import log +from backend.core.conf import settings +from backend.database.redis import RedisCli, redis_client + + +class CachePubSubManager: + """缓存 Pub/Sub 管理器""" + + _pubsub_task: asyncio.Task | None = None + + @staticmethod + async def publish_invalidation(key: str, *, is_delete_prefix: bool) -> None: + """ + 发布缓存失效通知 + + :param key: 缓存键 + :param is_delete_prefix: 是否删除符合前缀的所有缓存 + :return: + """ + try: + message = json.dumps({'key': key, 'is_delete_prefix': is_delete_prefix}) + await redis_client.publish(settings.CACHE_PUBSUB_CHANNEL, message) + except Exception as e: + log.warning(f'[CachePubSub] 发布通知失败: {e}') + + @staticmethod + async def subscribe_and_listen() -> None: # noqa: C901 + """订阅并监听缓存失效通知""" + reconnect_attempts = 0 + + while reconnect_attempts < settings.CACHE_PUBSUB_MAX_RECONNECT_ATTEMPTS: + pubsub_client: RedisCli | None = None + pubsub = None + + try: + # 使用独立连接 + pubsub_client = RedisCli() + pubsub = pubsub_client.pubsub() + await pubsub.subscribe(settings.CACHE_PUBSUB_CHANNEL) + + # 发布订阅成功 + reconnect_attempts = 0 + + async for message in pubsub.listen(): + if message['type'] == 'message': + try: + data = json.loads(message['data']) + key = data['key'] + if not data['is_delete_prefix']: + local_cache_manager.delete(key) + else: + local_cache_manager.delete_prefix(key) + except json.JSONDecodeError as e: + log.warning(f'[CachePubSub] 消息格式错误 {e}') + except Exception as e: + log.error(f'[CachePubSub] 处理通知失败: {e}') + + except asyncio.CancelledError: + break + except Exception as e: + reconnect_attempts += 1 + log.error( + f'[CachePubSub] 订阅异常 ({reconnect_attempts}/{settings.CACHE_PUBSUB_MAX_RECONNECT_ATTEMPTS}): {e}' + ) + + if reconnect_attempts >= settings.CACHE_PUBSUB_MAX_RECONNECT_ATTEMPTS: + log.error('[CachePubSub] 达到最大重连次数,停止订阅') + break + + await asyncio.sleep(settings.CACHE_PUBSUB_RECONNECT_DELAY) + finally: + if pubsub_client: + try: + await pubsub_client.aclose() + except Exception: + pass + if pubsub: + try: + await pubsub.aclose() + except Exception: + pass + + @classmethod + def start_listener(cls) -> None: + """启动缓存 Pub/Sub 监听器""" + if not settings.CACHE_LOCAL_ENABLED: + return + + if cls._pubsub_task is None or cls._pubsub_task.done(): + cls._pubsub_task = asyncio.create_task(cls.subscribe_and_listen()) + + @classmethod + async def stop_listener(cls) -> None: + """停止缓存 Pub/Sub 监听器""" + if cls._pubsub_task is None: + return + + if not cls._pubsub_task.done(): + cls._pubsub_task.cancel() + try: + await cls._pubsub_task + except asyncio.CancelledError: + pass + + cls._pubsub_task = None + + +cache_pubsub_manager = CachePubSubManager() diff --git a/backend/common/context.py b/backend/common/context.py index 834fcee..2b06d0a 100644 --- a/backend/common/context.py +++ b/backend/common/context.py @@ -21,6 +21,8 @@ class TypedContextProtocol(Protocol): permission: str | None language: str + user_id: int | None + class TypedContext(TypedContextProtocol, _Context): def __getattr__(self, name: str) -> Any: diff --git a/backend/common/enums.py b/backend/common/enums.py index 33ed2ed..e8f6aeb 100644 --- a/backend/common/enums.py +++ b/backend/common/enums.py @@ -32,70 +32,6 @@ class StrEnum(_EnumBase, str, Enum): """字符串枚举基类""" -class MenuType(IntEnum): - """菜单类型""" - - directory = 0 - menu = 1 - button = 2 - embedded = 3 - link = 4 - - -class RoleDataRuleOperatorType(IntEnum): - """数据规则运算符""" - - AND = 0 - OR = 1 - - -class RoleDataRuleExpressionType(IntEnum): - """数据规则表达式""" - - eq = 0 # == - ne = 1 # != - gt = 2 # > - ge = 3 # >= - lt = 4 # < - le = 5 # <= - in_ = 6 - not_in = 7 - - -class MethodType(StrEnum): - """HTTP 请求方法""" - - GET = 'GET' - POST = 'POST' - PUT = 'PUT' - DELETE = 'DELETE' - PATCH = 'PATCH' - OPTIONS = 'OPTIONS' - - -class LoginLogStatusType(IntEnum): - """登录日志状态""" - - fail = 0 - success = 1 - - -class BuildTreeType(StrEnum): - """构建树形结构类型""" - - traversal = 'traversal' - recursive = 'recursive' - - -class OperaLogCipherType(IntEnum): - """操作日志加密类型""" - - aes = 0 - md5 = 1 - itsdangerous = 2 - plan = 3 - - class StatusType(IntEnum): """状态类型""" diff --git a/backend/common/exception/errors.py b/backend/common/exception/errors.py index 77b2dcf..40302aa 100644 --- a/backend/common/exception/errors.py +++ b/backend/common/exception/errors.py @@ -16,6 +16,7 @@ def __init__(self, *, msg: str | None = None, data: Any = None, background: Back self.data = data # The original background task: https://www.starlette.io/background/ self.background = background + super().__init__(msg) class HTTPError(HTTPException): diff --git a/backend/common/log.py b/backend/common/log.py index 88a9bd0..429ffa7 100644 --- a/backend/common/log.py +++ b/backend/common/log.py @@ -4,6 +4,8 @@ import re import sys +from typing import Any + from loguru import logger from backend.core.conf import settings @@ -35,23 +37,36 @@ def emit(self, record: logging.LogRecord) -> None: logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage()) -def default_formatter(record: logging.LogRecord) -> str: - """默认日志格式化程序""" +def default_formatter(record: dict) -> str: + """ + 默认日志格式化程序 + :param record: Loguru Record 对象 + :return: + """ # 重写 sqlalchemy echo 输出 # https://github.com/sqlalchemy/sqlalchemy/discussions/12791 record_name = record['name'] or '' if record_name.startswith('sqlalchemy'): record['message'] = re.sub(r'\s+', ' ', record['message']).strip() - return settings.LOG_FORMAT if settings.LOG_FORMAT.endswith('\n') else f'{settings.LOG_FORMAT}\n' + base_format = settings.LOG_FORMAT if settings.LOG_FORMAT.endswith('\n') else f'{settings.LOG_FORMAT}\n' + if record.get('exception') is not None: + base_format += '{exception}\n' + + return base_format -def request_id_filter(record: logging.LogRecord) -> logging.LogRecord: - """请求 ID 过滤器""" +def request_id_filter(record: dict) -> bool: + """ + 请求 ID 过滤器 + + :param record: Loguru Record 对象 + :return: + """ rid = get_request_trace_id() record['request_id'] = rid[: settings.TRACE_ID_LOG_LENGTH] - return record + return True def setup_logging() -> None: @@ -84,14 +99,14 @@ def setup_logging() -> None: # 配置 loguru 处理器 logger.configure( - handlers=[ + handlers=[ # type: ignore[arg-type] { 'sink': sys.stdout, 'level': settings.LOG_STD_LEVEL, 'format': default_formatter, 'filter': lambda record: request_id_filter(record), - }, - ], + } + ] ) @@ -109,12 +124,12 @@ def compression(filepath: str) -> str: filename = filepath.split(os.sep)[-1] original_filename = filename.split('.')[0] if '-' in original_filename: - return LOG_DIR / f'{original_filename}.log' - return LOG_DIR / f'{original_filename}_{timezone.now().strftime("%Y-%m-%d")}.log' + return str(LOG_DIR / f'{original_filename}.log') + return str(LOG_DIR / f'{original_filename}_{timezone.now().strftime("%Y-%m-%d")}.log') # 日志文件通用配置 # https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add - log_config = { + log_config: dict[str, Any] = { 'format': default_formatter, 'enqueue': True, 'rotation': '00:00', diff --git a/backend/common/prometheus/instruments.py b/backend/common/prometheus/instruments.py deleted file mode 100644 index 9759ff6..0000000 --- a/backend/common/prometheus/instruments.py +++ /dev/null @@ -1,35 +0,0 @@ -from prometheus_client import Counter, Gauge, Histogram - -from backend.core.conf import settings - -PROMETHEUS_INFO_GAUGE = ( - Gauge(name='fba_app_info', documentation='fba 应用信息', labelnames=['app_name']) - .labels(app_name=settings.GRAFANA_APP_NAME) - .inc() -) - -PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE = Gauge( - 'fba_request_in_progress', - '按方法和路径统计请求的衡量', - ['app_name', 'method', 'path'], -) - -PROMETHEUS_REQUEST_COUNTER = Counter('fba_request_total', '按方法和路径统计请求总数', ['app_name', 'method', 'path']) - -PROMETHEUS_RESPONSE_COUNTER = Counter( - 'fba_response_total', - '按方法、路径和状态码统计响应总数', - ['app_name', 'method', 'path', 'status_code'], -) - -PROMETHEUS_EXCEPTION_COUNTER = Counter( - 'fba_exception_total', - '按方法,路径和异常类型统计异常总数', - ['app_name', 'method', 'path', 'exception_type'], -) - -PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM = Histogram( - 'fba_request_cost_time', - '按方法和路径划分请求耗时的直方图(以 ms 为单位)', - ['app_name', 'method', 'path'], -) diff --git a/backend/common/queue.py b/backend/common/queue.py deleted file mode 100644 index 9bc9172..0000000 --- a/backend/common/queue.py +++ /dev/null @@ -1,31 +0,0 @@ -import asyncio - -from asyncio import Queue - -from backend.common.log import log - - -async def batch_dequeue(queue: Queue, max_items: int, timeout: float) -> list: - """ - 从异步队列中获取多个项目 - - :param queue: 用于获取项目的 `asyncio.Queue` 队列 - :param max_items: 从队列中获取的最大项目数量 - :param timeout: 总的等待超时时间(秒) - :return: - """ - items = [] - - async def collector() -> None: - while len(items) < max_items: - item = await queue.get() - items.append(item) - - try: - await asyncio.wait_for(collector(), timeout=timeout) - except asyncio.TimeoutError: - pass - except Exception as e: - log.error(f'队列批量获取失败: {e}') - - return items diff --git a/backend/common/security/jwt.py b/backend/common/security/jwt.py index bb41abc..b428393 100644 --- a/backend/common/security/jwt.py +++ b/backend/common/security/jwt.py @@ -12,7 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from backend.app.admin.model import User -from backend.app.admin.schema.user import GetUserInfoWithRelationDetail +from backend.app.admin.schema.user import GetUserInfoDetail from backend.common.dataclasses import AccessToken, NewToken, RefreshToken, TokenPayload from backend.common.exception import errors from backend.core.conf import settings @@ -200,7 +200,7 @@ async def get_current_user(db: AsyncSession, pk: int) -> User: """ from backend.app.admin.crud.crud_user import user_dao - user = await user_dao.get_join(db, user_id=pk) + user = await user_dao.get(db, pk) if not user: raise errors.TokenError(msg='Token 无效') if not user.status: @@ -208,7 +208,7 @@ async def get_current_user(db: AsyncSession, pk: int) -> User: return user -async def get_jwt_user(user_id: int) -> GetUserInfoWithRelationDetail: +async def get_jwt_user(user_id: int) -> GetUserInfoDetail: """ 获取 JWT 用户 @@ -219,7 +219,7 @@ async def get_jwt_user(user_id: int) -> GetUserInfoWithRelationDetail: if not cache_user: async with async_db_session() as db: current_user = await get_current_user(db, user_id) - user = GetUserInfoWithRelationDetail.model_validate(current_user) + user = GetUserInfoDetail.model_validate(current_user) await redis_client.setex( f'{settings.JWT_USER_REDIS_PREFIX}:{user_id}', settings.TOKEN_EXPIRE_SECONDS, @@ -228,7 +228,7 @@ async def get_jwt_user(user_id: int) -> GetUserInfoWithRelationDetail: else: # TODO: 在恰当的时机,应替换为使用 model_validate_json # https://docs.pydantic.dev/latest/concepts/json/#partial-json-parsing - user = GetUserInfoWithRelationDetail.model_validate(from_json(cache_user, allow_partial=True)) + user = GetUserInfoDetail.model_validate(from_json(cache_user, allow_partial=True)) return user @@ -246,7 +246,7 @@ def superuser_verify(request: Request, _token: str = DependsJwtAuth) -> bool: return superuser -async def jwt_authentication(token: str) -> GetUserInfoWithRelationDetail: +async def jwt_authentication(token: str) -> GetUserInfoDetail: """ JWT 认证 diff --git a/backend/core/conf.py b/backend/core/conf.py index d0545f4..9e7ded4 100644 --- a/backend/core/conf.py +++ b/backend/core/conf.py @@ -68,6 +68,16 @@ def settings_customise_sources( # Redis REDIS_TIMEOUT: int = 5 + # 缓存 + CACHE_LOCAL_ENABLED: bool = True + CACHE_LOCAL_MAXSIZE: int = 100000 + CACHE_LOCAL_TTL: int = 60 * 60 * 2 # 2 小时 + CACHE_REDIS_TTL: int = 60 * 60 * 2 # 2 小时 + CACHE_CONFIG_REDIS_PREFIX: str = 'fba:cache:config' + CACHE_PUBSUB_CHANNEL: str = 'fba:cache:invalidate' + CACHE_PUBSUB_RECONNECT_DELAY: int = 5 # 重连延迟(秒) + CACHE_PUBSUB_MAX_RECONNECT_ATTEMPTS: int = 10 # 最大重连次数 + # .env Snowflake SNOWFLAKE_DATACENTER_ID: int | None = None SNOWFLAKE_WORKER_ID: int | None = None @@ -86,14 +96,20 @@ def settings_customise_sources( TOKEN_REFRESH_EXPIRE_SECONDS: int = 60 * 60 * 24 * 7 # 7 天 TOKEN_REDIS_PREFIX: str = 'fba:token' TOKEN_EXTRA_INFO_REDIS_PREFIX: str = 'fba:token_extra_info' + TOKEN_ONLINE_REDIS_PREFIX: str = 'fba:token_online' TOKEN_REFRESH_REDIS_PREFIX: str = 'fba:refresh_token' TOKEN_REQUEST_PATH_EXCLUDE: list[str] = [ # JWT / RBAC 路由白名单 f'{FASTAPI_API_V1_PATH}/auth/login', ] - TOKEN_REQUEST_PATH_EXCLUDE_PATTERN: list[Pattern[str]] = [ # JWT / RBAC 路由白名单(正则) - ] + TOKEN_REQUEST_PATH_EXCLUDE_PATTERN: list[Pattern[str]] = [] # JWT / RBAC 路由白名单(正则) # 用户安全 + USER_LOCK_REDIS_PREFIX: str = 'fba:user:lock' + USER_LOCK_THRESHOLD: int = 5 # 用户密码错误锁定阈值,0 表示禁用锁定 + USER_LOCK_SECONDS: int = 60 * 5 # 5 分钟 + USER_PASSWORD_EXPIRY_DAYS: int = 365 # 用户密码有效期,0 表示永不过期 + USER_PASSWORD_REMINDER_DAYS: int = 7 # 用户密码到期提醒,0 表示不提醒 + USER_PASSWORD_HISTORY_CHECK_COUNT: int = 3 USER_PASSWORD_MIN_LENGTH: int = 6 USER_PASSWORD_MAX_LENGTH: int = 32 USER_PASSWORD_REQUIRE_SPECIAL_CHAR: bool = False @@ -102,6 +118,7 @@ def settings_customise_sources( LOGIN_CAPTCHA_ENABLED: bool = True LOGIN_CAPTCHA_REDIS_PREFIX: str = 'fba:login:captcha' LOGIN_CAPTCHA_EXPIRE_SECONDS: int = 60 * 5 # 5 分钟 + LOGIN_FAILURE_PREFIX: str = 'fba:login:failure' # JWT JWT_USER_REDIS_PREFIX: str = 'fba:user' @@ -110,9 +127,15 @@ def settings_customise_sources( COOKIE_REFRESH_TOKEN_KEY: str = 'fba_refresh_token' COOKIE_REFRESH_TOKEN_EXPIRE_SECONDS: int = 60 * 60 * 24 * 7 # 7 天 + # 插件 + PLUGIN_PIP_CHINA: bool = True + PLUGIN_PIP_INDEX_URL: str = 'https://mirrors.aliyun.com/pypi/simple/' + PLUGIN_PIP_MAX_RETRY: int = 3 + PLUGIN_REDIS_PREFIX: str = 'fba:plugin' + # CORS CORS_ALLOWED_ORIGINS: list[str] = [ # 末尾不带斜杠 - 'http://127.0.0.1:8000', + 'http://127.0.0.1', 'http://localhost:5173', ] CORS_EXPOSE_HEADERS: list[str] = [ @@ -169,37 +192,9 @@ def settings_customise_sources( LOG_ACCESS_FILENAME: str = 'fba_access.log' LOG_ERROR_FILENAME: str = 'fba_error.log' - # 操作日志 - OPERA_LOG_PATH_EXCLUDE: list[str] = [ - '/favicon.ico', - '/docs', - '/redoc', - '/openapi', - f'{FASTAPI_API_V1_PATH}/auth/login/swagger', - ] - OPERA_LOG_REDACT_KEYS: list[str] = [ - 'password', - 'old_password', - 'new_password', - 'confirm_password', - ] - OPERA_LOG_QUEUE_BATCH_CONSUME_SIZE: int = 100 - OPERA_LOG_QUEUE_TIMEOUT: int = 60 # 1 分钟 - - # Plugin 配置 - PLUGIN_PIP_CHINA: bool = True - PLUGIN_PIP_INDEX_URL: str = 'https://mirrors.aliyun.com/pypi/simple/' - PLUGIN_PIP_MAX_RETRY: int = 3 - PLUGIN_REDIS_PREFIX: str = 'fba:plugin' - # I18n 配置 I18N_DEFAULT_LANGUAGE: str = 'zh-CN' - # Grafana - GRAFANA_METRICS: bool = False - GRAFANA_APP_NAME: str = 'fba_server' - GRAFANA_OTLP_GRPC_ENDPOINT: str = 'fba_alloy:4317' - @model_validator(mode='before') @classmethod def check_env(cls, values: Any) -> Any: diff --git a/backend/core/registrar.py b/backend/core/registrar.py index 1b43e1c..7c1b716 100644 --- a/backend/core/registrar.py +++ b/backend/core/registrar.py @@ -1,13 +1,10 @@ import os -from asyncio import create_task from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from fastapi import Depends, FastAPI -from fastapi_limiter import FastAPILimiter from fastapi_pagination import add_pagination -from prometheus_client import make_asgi_app from starlette.middleware.authentication import AuthenticationMiddleware from starlette.middleware.cors import CORSMiddleware from starlette.staticfiles import StaticFiles @@ -15,6 +12,7 @@ from starlette_context.plugins import RequestIdPlugin from backend import __version__ +from backend.common.cache.pubsub import cache_pubsub_manager from backend.common.exception.exception_handler import register_exception from backend.common.log import set_custom_logfile, setup_logging from backend.common.response.response_code import StandardResponseCode @@ -29,12 +27,9 @@ from backend.middleware.state_middleware import StateMiddleware from backend.plugin.core import build_final_router from backend.utils.demo_mode import demo_site -from backend.utils.limiter import http_limit_callback from backend.utils.openapi import ensure_unique_route_names, simplify_operation_ids -from backend.utils.otel import init_otel from backend.utils.serializers import MsgSpecJSONResponse from backend.utils.snowflake import snowflake -from backend.utils.trace_id import OtelTraceIdPlugin @asynccontextmanager @@ -51,21 +46,17 @@ async def register_init(app: FastAPI) -> AsyncGenerator[None, None]: # 初始化 redis await redis_client.init() - # 初始化 limiter - await FastAPILimiter.init( - redis=redis_client, - prefix=settings.REQUEST_LIMITER_REDIS_PREFIX, - http_callback=http_limit_callback, - ) - # 初始化 snowflake 节点 await snowflake.init() - # 创建操作日志任务 - create_task(OperaLogMiddleware.consumer()) + # 启动缓存 Pub/Sub 监听器 + cache_pubsub_manager.start_listener() yield + # 停止缓存 Pub/Sub 监听器 + await cache_pubsub_manager.stop_listener() + # 释放 snowflake 节点 await snowflake.shutdown() @@ -95,9 +86,6 @@ def register_app() -> FastAPI: register_page(app) register_exception(app) - if settings.GRAFANA_METRICS: - register_metrics(app) - return app @@ -151,10 +139,9 @@ def register_middleware(app: FastAPI) -> None: app.add_middleware(AccessMiddleware) # ContextVar - plugins = [OtelTraceIdPlugin()] if settings.GRAFANA_METRICS else [RequestIdPlugin(validate=True)] app.add_middleware( ContextMiddleware, - plugins=plugins, + plugins=[RequestIdPlugin(validate=True)], default_error_response=MsgSpecJSONResponse( content={'code': StandardResponseCode.HTTP_400, 'msg': 'BAD_REQUEST', 'data': None}, status_code=StandardResponseCode.HTTP_400, @@ -201,16 +188,3 @@ def register_page(app: FastAPI) -> None: :return: """ add_pagination(app) - - -def register_metrics(app: FastAPI) -> None: - """ - 注册指标 - - :param app: FastAPI 应用实例 - :return: - """ - metrics_app = make_asgi_app() - app.mount('/metrics', metrics_app) - - init_otel(app) diff --git a/backend/middleware/access_middleware.py b/backend/middleware/access_middleware.py index c1f60db..28b873a 100644 --- a/backend/middleware/access_middleware.py +++ b/backend/middleware/access_middleware.py @@ -19,10 +19,11 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) - :param call_next: 下一个中间件或路由处理函数 :return: """ - path = request.url.path if not request.url.query else request.url.path + '/' + request.url.query + path = request.url.path + method = request.method - if request.method != 'OPTIONS': - log.debug(f'--> 请求开始[{path}]') + if method != 'OPTIONS': + log.debug(f'--> 请求开始[{path if not request.url.query else request.url.path + "/" + request.url.query}]') perf_time = time.perf_counter() ctx.perf_time = perf_time diff --git a/backend/middleware/jwt_auth_middleware.py b/backend/middleware/jwt_auth_middleware.py index 081f867..408f5ac 100644 --- a/backend/middleware/jwt_auth_middleware.py +++ b/backend/middleware/jwt_auth_middleware.py @@ -6,7 +6,8 @@ from starlette.authentication import AuthenticationError as StarletteAuthenticationError from starlette.requests import HTTPConnection -from backend.app.admin.schema.user import GetUserInfoWithRelationDetail +from backend.app.admin.schema.user import GetUserInfoDetail +from backend.common.context import ctx from backend.common.exception.errors import TokenError from backend.common.log import log from backend.common.security.jwt import jwt_authentication @@ -76,7 +77,7 @@ def extract_token(request: Request) -> str | None: return token - async def authenticate(self, request: Request) -> tuple[AuthCredentials, GetUserInfoWithRelationDetail] | None: + async def authenticate(self, request: Request) -> tuple[AuthCredentials, GetUserInfoDetail] | None: """ 认证请求 @@ -95,6 +96,9 @@ async def authenticate(self, request: Request) -> tuple[AuthCredentials, GetUser log.exception(f'JWT 授权异常:{e}') raise AuthenticationError(code=getattr(e, 'code', 500), msg=getattr(e, 'msg', 'Internal Server Error')) + # 设置用户 ID 到上下文 + ctx.user_id = user.id + # 请注意,此返回使用非标准模式,所以在认证通过时,将丢失某些标准特性 # 标准返回模式请查看:https://www.starlette.io/authentication/ return AuthCredentials(['authenticated']), user diff --git a/backend/middleware/opera_log_middleware.py b/backend/middleware/opera_log_middleware.py index c4f86e3..f428936 100644 --- a/backend/middleware/opera_log_middleware.py +++ b/backend/middleware/opera_log_middleware.py @@ -1,38 +1,20 @@ -import json import time -from asyncio import Queue from typing import Any from fastapi import Response -from starlette.datastructures import UploadFile from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request -from backend.app.admin.schema.opera_log import CreateOperaLogParam -from backend.app.admin.service.opera_log_service import opera_log_service from backend.common.context import ctx -from backend.common.enums import StatusType from backend.common.log import log -from backend.common.prometheus.instruments import ( - PROMETHEUS_EXCEPTION_COUNTER, - PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM, - PROMETHEUS_REQUEST_COUNTER, - PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE, - PROMETHEUS_RESPONSE_COUNTER, -) -from backend.common.queue import batch_dequeue from backend.common.response.response_code import StandardResponseCode from backend.core.conf import settings -from backend.database.db import async_db_session -from backend.utils.trace_id import get_request_trace_id class OperaLogMiddleware(BaseHTTPMiddleware): """操作日志中间件""" - opera_log_queue: Queue = Queue(maxsize=100000) - async def dispatch(self, request: Request, call_next: Any) -> Response: """ 处理请求并记录操作日志 @@ -41,231 +23,43 @@ async def dispatch(self, request: Request, call_next: Any) -> Response: :param call_next: 下一个中间件或路由处理函数 :return: """ - response = None path = request.url.path + method = request.method + code = 200 + elapsed = 0 - if path in settings.OPERA_LOG_PATH_EXCLUDE or not path.startswith(f'{settings.FASTAPI_API_V1_PATH}'): + try: response = await call_next(request) + except Exception as e: + elapsed = round((time.perf_counter() - ctx.perf_time) * 1000, 3) + code = getattr(e, 'code', StandardResponseCode.HTTP_500) + log.error(f'请求异常: {e!s}') + raise else: - method = request.method - args = await self.get_request_args(request) - PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE.labels( - app_name=settings.GRAFANA_APP_NAME, method=method, path=path - ).inc() - PROMETHEUS_REQUEST_COUNTER.labels(app_name=settings.GRAFANA_APP_NAME, method=method, path=path).inc() - - # 执行请求 - code = 200 - msg = 'Success' - status = StatusType.enable - error = None - try: - response = await call_next(request) - elapsed = round((time.perf_counter() - ctx.perf_time) * 1000, 3) - for e in [ - '__request_http_exception__', - '__request_validation_exception__', - '__request_assertion_error__', - '__request_custom_exception__', - ]: - exception = ctx.get(e) - if exception: - code = exception.get('code') - msg = exception.get('msg') - log.error(f'请求异常: {msg}') - PROMETHEUS_EXCEPTION_COUNTER.labels( - app_name=settings.GRAFANA_APP_NAME, - method=method, - path=path, - exception_type=type(e).__name__, - ).inc() - break - except Exception as e: - elapsed = round((time.perf_counter() - ctx.perf_time) * 1000, 3) - code = getattr(e, 'code', StandardResponseCode.HTTP_500) # 兼容 SQLAlchemy 异常用法 - msg = getattr(e, 'msg', str(e)) # 不建议使用 traceback 模块获取错误信息,会暴漏代码信息 - status = StatusType.disable - error = e - log.error(f'请求异常: {e!s}') - PROMETHEUS_EXCEPTION_COUNTER.labels( - app_name=settings.GRAFANA_APP_NAME, method=method, path=path, exception_type=type(e).__name__ - ).inc() - else: - PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM.labels( - app_name=settings.GRAFANA_APP_NAME, method=method, path=path - ).observe(elapsed, exemplar={'TraceID': get_request_trace_id()}) - finally: - PROMETHEUS_RESPONSE_COUNTER.labels( - app_name=settings.GRAFANA_APP_NAME, method=method, path=path, status_code=code - ).inc() - PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE.labels( - app_name=settings.GRAFANA_APP_NAME, method=method, path=path - ).dec() - - # 此信息只能在请求后获取 + elapsed = round((time.perf_counter() - ctx.perf_time) * 1000, 3) + + for exception_key in [ + '__request_http_exception__', + '__request_validation_exception__', + '__request_assertion_error__', + '__request_custom_exception__', + ]: + exception = ctx.get(exception_key) + if exception: + code = exception.get('code') + log.error(f'请求异常: {exception.get("msg")}') + break + finally: route = request.scope.get('route') summary = route.summary or '' if route else '' - try: - # 此信息来源于 JWT 认证中间件 - username = request.user.username - except AttributeError: - username = None - - # 日志记录 log.debug(f'接口摘要:[{summary}]') log.debug(f'请求地址:[{ctx.ip}]') - log.debug(f'请求参数:{args}') - log.info(f'{ctx.ip: <15} | {request.method: <8} | {code!s: <6} | {path} | {elapsed:.3f}ms') + if request.method != 'OPTIONS': log.debug('<-- 请求结束') - # 日志创建 - opera_log_in = CreateOperaLogParam( - trace_id=get_request_trace_id(), - username=username, - method=method, - title=summary, - path=path, - ip=ctx.ip, - country=ctx.country, - region=ctx.region, - city=ctx.city, - user_agent=ctx.user_agent, - os=ctx.os, - browser=ctx.browser, - device=ctx.device, - args=args, - status=status, - code=str(code), - msg=msg, - cost_time=elapsed, # 可能和日志存在微小差异(可忽略) - opera_time=ctx.start_time, - ) - await self.opera_log_queue.put(opera_log_in) - - # 错误抛出 - if error: - raise error from None + if path.startswith(settings.FASTAPI_API_V1_PATH): + log.info(f'{ctx.ip: <15} | {method: <8} | {code!s: <6} | {path} | {elapsed:.3f}ms') return response - - async def get_request_args(self, request: Request) -> dict[str, Any] | None: # noqa: C901 - """ - 获取请求参数 - - :param request: FastAPI 请求对象 - :return: - """ - args = {} - - # 查询参数 - query_params = dict(request.query_params) - if query_params: - args['query_params'] = self.desensitization(query_params) - - # 路径参数 - path_params = request.path_params - if path_params: - args['path_params'] = self.desensitization(path_params) - - # Tip: .body() 必须在 .form() 之前获取 - # https://github.com/encode/starlette/discussions/1933 - content_type = request.headers.get('Content-Type', '').split(';') - - # 请求体 - body_data = await request.body() - if body_data: - # 注意:非 json 数据默认使用 data 作为键 - if 'application/json' not in content_type: - args['data'] = body_data.decode('utf-8', 'ignore') if isinstance(body_data, bytes) else str(body_data) - else: - json_data = await request.json() - if isinstance(json_data, dict): - args['json'] = self.desensitization(json_data) - else: - args['data'] = str(json_data) - - # 表单参数 - form_data = await request.form() - if len(form_data) > 0: - serialized_form = {} - for k, v in form_data.items(): - if isinstance(v, UploadFile): - serialized_form[k] = { - 'filename': v.filename, - 'content_type': v.content_type, - 'size': v.size, - } - else: - serialized_form[k] = v - if 'multipart/form-data' not in content_type: - args['x-www-form-urlencoded'] = self.desensitization(serialized_form) - else: - args['form-data'] = self.desensitization(serialized_form) - - if args: - args = self.truncate(args) - return args or None - - @staticmethod - def truncate(args: dict[str, Any]) -> dict[str, Any]: - """ - 截断处理 - - :param args: 需要截断的请求参数字典 - :return: - """ - max_size = 10240 # 数据最大大小(字节) - - try: - args_str = json.dumps(args, ensure_ascii=False) - args_size = len(args_str.encode('utf-8')) - - if args_size > max_size: - truncated_str = args_str[:max_size] - return { - '_truncated': True, - '_original_size': args_size, - '_max_size': max_size, - '_message': f'数据过大已截断:原始大小 {args_size} 字节,限制 {max_size} 字节', - 'data_preview': truncated_str, - } - except Exception as e: - log.error(f'请求参数截断处理失败:{e}') - - return args - - @staticmethod - def desensitization(args: dict[str, Any]) -> dict[str, Any]: - """ - 脱敏处理 - - :param args: 需要脱敏的参数字典 - :return: - """ - for key in args: - if key in settings.OPERA_LOG_REDACT_KEYS: - args[key] = '[REDACTED]' - return args - - @classmethod - async def consumer(cls) -> None: - """操作日志消费者""" - while True: - logs = await batch_dequeue( - cls.opera_log_queue, - max_items=settings.OPERA_LOG_QUEUE_BATCH_CONSUME_SIZE, - timeout=settings.OPERA_LOG_QUEUE_TIMEOUT, - ) - if logs: - try: - if settings.DATABASE_ECHO: - log.info('自动执行【操作日志批量创建】任务...') - async with async_db_session.begin() as db: - await opera_log_service.bulk_create(db=db, objs=logs) - except Exception as e: - log.error(f'操作日志入库失败,丢失 {len(logs)} 条日志: {e}') - finally: - for _ in range(len(logs)): - cls.opera_log_queue.task_done() diff --git a/backend/plugin/config/plugin.toml b/backend/plugin/config/plugin.toml index 264fea7..adf7b3b 100644 --- a/backend/plugin/config/plugin.toml +++ b/backend/plugin/config/plugin.toml @@ -1,14 +1,14 @@ [plugin] -summary = '参数配置' -version = '0.0.2' -description = '通常用于动态配置系统参数和前端工程数据展示' -author = 'wu-clan' -tags = ['other'] -database = ['mysql', 'postgresql'] +summary = "参数配置" +version = "0.0.2" +description = "通常用于动态配置系统参数和前端工程数据展示" +author = "wu-clan" +tags = ["other"] +database = ["mysql", "postgresql"] [app] -extend = 'admin' +extend = "admin" [api.config] -prefix = '/configs' -tags = '系统参数配置' +prefix = "/configs" +tags = "系统参数配置" diff --git a/backend/plugin/config/service/config_service.py b/backend/plugin/config/service/config_service.py index 23c3e03..aed7727 100644 --- a/backend/plugin/config/service/config_service.py +++ b/backend/plugin/config/service/config_service.py @@ -3,8 +3,10 @@ from sqlalchemy.ext.asyncio import AsyncSession +from backend.common.cache.decorator import cache_invalidate, cached from backend.common.exception import errors from backend.common.pagination import paging_data +from backend.core.conf import settings from backend.plugin.config.crud.crud_config import config_dao from backend.plugin.config.model import Config from backend.plugin.config.schema.config import ( @@ -18,6 +20,7 @@ class ConfigService: """参数配置服务类""" @staticmethod + @cached(settings.CACHE_CONFIG_REDIS_PREFIX, key='pk') async def get(*, db: AsyncSession, pk: int) -> Config: """ 获取参数配置详情 @@ -26,13 +29,13 @@ async def get(*, db: AsyncSession, pk: int) -> Config: :param pk: 参数配置 ID :return: """ - config = await config_dao.get(db, pk) if not config: raise errors.NotFoundError(msg='参数配置不存在') return config @staticmethod + @cached(settings.CACHE_CONFIG_REDIS_PREFIX, key='type') async def get_all(*, db: AsyncSession, type: str | None) -> Sequence[Config | None]: """ 获取所有参数配置 @@ -41,7 +44,6 @@ async def get_all(*, db: AsyncSession, type: str | None) -> Sequence[Config | No :param type: 参数配置类型 :return: """ - return await config_dao.get_all(db, type) @staticmethod @@ -66,13 +68,13 @@ async def create(*, db: AsyncSession, obj: CreateConfigParam) -> None: :param obj: 参数配置创建参数 :return: """ - config = await config_dao.get_by_key(db, obj.key) if config: raise errors.ConflictError(msg=f'参数配置 {obj.key} 已存在') await config_dao.create(db, obj) @staticmethod + @cache_invalidate(settings.CACHE_CONFIG_REDIS_PREFIX) async def update(*, db: AsyncSession, pk: int, obj: UpdateConfigParam) -> int: """ 更新参数配置 @@ -82,7 +84,6 @@ async def update(*, db: AsyncSession, pk: int, obj: UpdateConfigParam) -> int: :param obj: 参数配置更新参数 :return: """ - config = await config_dao.get(db, pk) if not config: raise errors.NotFoundError(msg='参数配置不存在') @@ -94,6 +95,7 @@ async def update(*, db: AsyncSession, pk: int, obj: UpdateConfigParam) -> int: return count @staticmethod + @cache_invalidate(settings.CACHE_CONFIG_REDIS_PREFIX) async def bulk_update(*, db: AsyncSession, objs: list[UpdateConfigsParam]) -> int: """ 批量更新参数配置 @@ -102,7 +104,6 @@ async def bulk_update(*, db: AsyncSession, objs: list[UpdateConfigsParam]) -> in :param objs: 参数配置批量更新参数 :return: """ - for _batch in range(0, len(objs), 1000): for obj in objs: config = await config_dao.get(db, obj.id) @@ -116,6 +117,7 @@ async def bulk_update(*, db: AsyncSession, objs: list[UpdateConfigsParam]) -> in return count @staticmethod + @cache_invalidate(settings.CACHE_CONFIG_REDIS_PREFIX) async def delete(*, db: AsyncSession, pks: list[int]) -> int: """ 批量删除参数配置 @@ -124,7 +126,6 @@ async def delete(*, db: AsyncSession, pks: list[int]) -> int: :param pks: 参数配置 ID 列表 :return: """ - count = await config_dao.delete(db, pks) return count diff --git a/backend/plugin/config/sql/mysql/destroy.sql b/backend/plugin/config/sql/mysql/destroy.sql new file mode 100644 index 0000000..505da1c --- /dev/null +++ b/backend/plugin/config/sql/mysql/destroy.sql @@ -0,0 +1,5 @@ +delete from sys_menu where name in ('AddConfig', 'EditConfig', 'DeleteConfig'); + +delete from sys_menu where name = 'PluginConfig'; + +drop table if exists sys_config; diff --git a/backend/plugin/config/sql/mysql/destroy_snowflake.sql b/backend/plugin/config/sql/mysql/destroy_snowflake.sql new file mode 100644 index 0000000..505da1c --- /dev/null +++ b/backend/plugin/config/sql/mysql/destroy_snowflake.sql @@ -0,0 +1,5 @@ +delete from sys_menu where name in ('AddConfig', 'EditConfig', 'DeleteConfig'); + +delete from sys_menu where name = 'PluginConfig'; + +drop table if exists sys_config; diff --git a/backend/plugin/config/sql/mysql/init.sql b/backend/plugin/config/sql/mysql/init.sql index 4c89702..1b08336 100644 --- a/backend/plugin/config/sql/mysql/init.sql +++ b/backend/plugin/config/sql/mysql/init.sql @@ -1,3 +1,16 @@ +set @system_menu_id = (select id from sys_menu where name = 'System'); + +insert into sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) +values ('config.menu', 'PluginConfig', '/plugins/config', 7, 'codicon:symbol-parameter', 1, '/plugins/config/views/index', null, 1, 1, 1, '', null, @system_menu_id, now(), null); + +set @config_menu_id = LAST_INSERT_ID(); + +insert into sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) +values +('新增', 'AddConfig', null, 0, null, 2, null, 'sys:config:add', 1, 0, 1, '', null, @config_menu_id, now(), null), +('修改', 'EditConfig', null, 0, null, 2, null, 'sys:config:edit', 1, 0, 1, '', null, @config_menu_id, now(), null), +('删除', 'DeleteConfig', null, 0, null, 2, null, 'sys:config:del', 1, 0, 1, '', null, @config_menu_id, now(), null); + insert into sys_config (id, name, type, `key`, value, is_frontend, remark, created_time, updated_time) values (1, '状态', 'EMAIL', 'EMAIL_CONFIG_STATUS', '1', false, null, now(), null), diff --git a/backend/plugin/config/sql/mysql/init_snowflake.sql b/backend/plugin/config/sql/mysql/init_snowflake.sql index 96bab30..c146f0f 100644 --- a/backend/plugin/config/sql/mysql/init_snowflake.sql +++ b/backend/plugin/config/sql/mysql/init_snowflake.sql @@ -1,3 +1,12 @@ +insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) +values (2049629108253622283, 'config.menu', 'PluginConfig', '/plugins/config', 7, 'codicon:symbol-parameter', 1, '/plugins/config/views/index', null, 1, 1, 1, '', null, 2049629108245233667, now(), null); + +insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) +values +(2049629108253622284, '新增', 'AddConfig', null, 0, null, 2, null, 'sys:config:add', 1, 0, 1, '', null, 2049629108253622283, now(), null), +(2049629108253622285, '修改', 'EditConfig', null, 0, null, 2, null, 'sys:config:edit', 1, 0, 1, '', null, 2049629108253622283, now(), null), +(2049629108253622286, '删除', 'DeleteConfig', null, 0, null, 2, null, 'sys:config:del', 1, 0, 1, '', null, 2049629108253622283, now(), null); + insert into sys_config (id, name, type, `key`, value, is_frontend, remark, created_time, updated_time) values (2069061886627938304, '状态', 'EMAIL', 'EMAIL_CONFIG_STATUS', '1', false, null, now(), null), diff --git a/backend/plugin/config/sql/postgresql/destroy.sql b/backend/plugin/config/sql/postgresql/destroy.sql new file mode 100644 index 0000000..c4b0dc6 --- /dev/null +++ b/backend/plugin/config/sql/postgresql/destroy.sql @@ -0,0 +1,7 @@ +delete from sys_menu where name in ('AddConfig', 'EditConfig', 'DeleteConfig'); + +delete from sys_menu where name = 'PluginConfig'; + +drop table if exists sys_config; + +select setval(pg_get_serial_sequence('sys_menu', 'id'), coalesce(max(id), 0) + 1, true) from sys_menu; diff --git a/backend/plugin/config/sql/postgresql/destroy_snowflake.sql b/backend/plugin/config/sql/postgresql/destroy_snowflake.sql new file mode 100644 index 0000000..505da1c --- /dev/null +++ b/backend/plugin/config/sql/postgresql/destroy_snowflake.sql @@ -0,0 +1,5 @@ +delete from sys_menu where name in ('AddConfig', 'EditConfig', 'DeleteConfig'); + +delete from sys_menu where name = 'PluginConfig'; + +drop table if exists sys_config; diff --git a/backend/plugin/config/sql/postgresql/init.sql b/backend/plugin/config/sql/postgresql/init.sql index dbe37dd..f5ec74d 100644 --- a/backend/plugin/config/sql/postgresql/init.sql +++ b/backend/plugin/config/sql/postgresql/init.sql @@ -1,3 +1,20 @@ +do $$ +declare + config_menu_id bigint; +begin + insert into sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) + values ('config.menu', 'PluginConfig', '/plugins/config', 7, 'codicon:symbol-parameter', 1, '/plugins/config/views/index', null, 1, 1, 1, '', null, (select id from sys_menu where name = 'System'), now(), null) + returning id into config_menu_id; + + insert into sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) + values + ('新增', 'AddConfig', null, 0, null, 2, null, 'sys:config:add', 1, 0, 1, '', null, config_menu_id, now(), null), + ('修改', 'EditConfig', null, 0, null, 2, null, 'sys:config:edit', 1, 0, 1, '', null, config_menu_id, now(), null), + ('删除', 'DeleteConfig', null, 0, null, 2, null, 'sys:config:del', 1, 0, 1, '', null, config_menu_id, now(), null); +end $$; + +select setval(pg_get_serial_sequence('sys_menu', 'id'), coalesce(max(id), 0) + 1, true) from sys_menu; + insert into sys_config (id, name, type, "key", value, is_frontend, remark, created_time, updated_time) values (1, '状态', 'EMAIL', 'EMAIL_CONFIG_STATUS', '1', false, null, now(), null), diff --git a/backend/plugin/config/sql/postgresql/init_snowflake.sql b/backend/plugin/config/sql/postgresql/init_snowflake.sql index ba2c50f..e4b7621 100644 --- a/backend/plugin/config/sql/postgresql/init_snowflake.sql +++ b/backend/plugin/config/sql/postgresql/init_snowflake.sql @@ -1,3 +1,12 @@ +insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) +values (2049629108253622283, 'config.menu', 'PluginConfig', '/plugins/config', 7, 'codicon:symbol-parameter', 1, '/plugins/config/views/index', null, 1, 1, 1, '', null, 2049629108245233667, now(), null); + +insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) +values +(2049629108253622284, '新增', 'AddConfig', null, 0, null, 2, null, 'sys:config:add', 1, 0, 1, '', null, 2049629108253622283, now(), null), +(2049629108253622285, '修改', 'EditConfig', null, 0, null, 2, null, 'sys:config:edit', 1, 0, 1, '', null, 2049629108253622283, now(), null), +(2049629108253622286, '删除', 'DeleteConfig', null, 0, null, 2, null, 'sys:config:del', 1, 0, 1, '', null, 2049629108253622283, now(), null); + insert into sys_config (id, name, type, "key", value, is_frontend, remark, created_time, updated_time) values (2069061886627938304, '状态', 'EMAIL', 'EMAIL_CONFIG_STATUS', '1', false, null, now(), null), diff --git a/backend/plugin/core.py b/backend/plugin/core.py index 1576b6a..2a0ea38 100644 --- a/backend/plugin/core.py +++ b/backend/plugin/core.py @@ -22,8 +22,8 @@ from backend.utils.dynamic_import import get_model_objects, import_module_cached -@lru_cache -def get_plugins() -> list[str]: +@lru_cache(maxsize=128) +def get_plugins() -> tuple[str, ...]: """获取插件列表""" plugin_packages = [] @@ -37,7 +37,7 @@ def get_plugins() -> list[str]: if os.path.isdir(item_path) and '__init__.py' in os.listdir(item_path): plugin_packages.append(item) - return plugin_packages + return tuple(plugin_packages) def get_plugin_models() -> list[object]: @@ -82,6 +82,37 @@ async def get_plugin_sql(plugin: str, db_type: DataBaseType, pk_type: PrimaryKey return sql_file +async def get_plugin_destroy_sql(plugin: str, db_type: DataBaseType, pk_type: PrimaryKeyType) -> str | None: + """ + 获取插件销毁 SQL 脚本 + + :param plugin: 插件名称 + :param db_type: 数据库类型 + :param pk_type: 主键类型 + :return: + """ + if db_type == DataBaseType.mysql: + mysql_dir = PLUGIN_DIR / plugin / 'sql' / 'mysql' + sql_file = ( + mysql_dir / 'destroy.sql' + if pk_type == PrimaryKeyType.autoincrement + else mysql_dir / 'destroy_snowflake.sql' + ) + else: + postgresql_dir = PLUGIN_DIR / plugin / 'sql' / 'postgresql' + sql_file = ( + postgresql_dir / 'destroy.sql' + if pk_type == PrimaryKeyType.autoincrement + else postgresql_dir / 'destroy_snowflake.sql' + ) + + path = anyio.Path(sql_file) + if not await path.exists(): + return None + + return sql_file + + def load_plugin_config(plugin: str) -> dict[str, Any]: """ 加载插件配置 @@ -104,7 +135,7 @@ def parse_plugin_config() -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: plugins = get_plugins() - # 使用独立单例,避免与主线程冲突 + # 使用独立连接 current_redis_client = RedisCli() run_await(current_redis_client.init)() diff --git a/backend/plugin/installer.py b/backend/plugin/installer.py index 326416b..80504de 100644 --- a/backend/plugin/installer.py +++ b/backend/plugin/installer.py @@ -1,6 +1,7 @@ import io import os import re +import stat import zipfile import anyio @@ -104,7 +105,7 @@ async def install_zip_plugin(file: UploadFile | str) -> str: await _append_env_example(full_plugin_path) await install_requirements_async(plugin_dir_name) - await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'ture') + await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'true') return plugin_name @@ -118,7 +119,7 @@ async def install_git_plugin(repo_url: str) -> str: """ match = is_git_url(repo_url) if not match: - raise errors.RequestError(msg='Git 仓库地址格式非法') + raise errors.RequestError(msg='Git 仓库地址格式非法,仅支持 HTTP/HTTPS 协议') repo_name = match.group('repo') path = anyio.Path(PLUGIN_DIR / repo_name) if await path.exists(): @@ -133,6 +134,40 @@ async def install_git_plugin(repo_url: str) -> str: await _append_env_example(path) await install_requirements_async(repo_name) - await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'ture') + await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'true') return repo_name + + +def remove_plugin(plugin_dir: os.PathLike) -> None: + """ + 删除插件 + + :param plugin_dir: 插件目录 + :return: + """ + import shutil + + def _on_error(func, path, _exc_info) -> None: # noqa: ANN001 + os.chmod(path, stat.S_IWRITE) + func(path) + + shutil.rmtree(plugin_dir, onerror=_on_error) + + +def zip_plugin(plugin_dir: os.PathLike, target: os.PathLike | io.BytesIO) -> None: + """ + zip 压缩插件 + + :param plugin_dir: 插件目录 + :param target: 压缩目标 + :return: + """ + with zipfile.ZipFile(target, 'w') as zf: + plugin_dir_parent = os.path.dirname(plugin_dir) + for root, dirs, files in os.walk(plugin_dir): + dirs[:] = [d for d in dirs if d != '__pycache__'] + for file in files: + file_path = os.path.join(root, file) + arcname = os.path.relpath(file_path, start=plugin_dir_parent) + zf.write(file_path, arcname) diff --git a/backend/plugin/requirements.py b/backend/plugin/requirements.py index ab89966..5554969 100644 --- a/backend/plugin/requirements.py +++ b/backend/plugin/requirements.py @@ -9,20 +9,10 @@ from backend.core.conf import settings from backend.core.path_conf import PLUGIN_DIR +from backend.plugin.core import get_plugins from backend.plugin.errors import PluginInstallError -def get_plugins() -> list[str]: - """ - 获取插件列表 - - 注意:此函数从 backend.plugin.core 导入以避免循环依赖 - """ - from backend.plugin.core import get_plugins as _get_plugins - - return _get_plugins() - - def _is_in_virtualenv() -> bool: """检测当前是否在虚拟环境中运行""" return hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix) @@ -57,32 +47,25 @@ def install_requirements(plugin: str | None) -> None: # noqa: C901 missing_dependencies = True if missing_dependencies: - try: - pip_install = ['uv', 'pip', 'install', '-r', requirements_file] - if not _is_in_virtualenv(): - pip_install.append('--system') - if settings.PLUGIN_PIP_CHINA: - pip_install.extend(['-i', settings.PLUGIN_PIP_INDEX_URL]) - - max_retries = settings.PLUGIN_PIP_MAX_RETRY - for attempt in range(max_retries): - try: - subprocess.check_call( - pip_install, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - break - except subprocess.TimeoutExpired: - if attempt == max_retries - 1: - raise PluginInstallError(f'插件 {plugin} 依赖安装超时') - continue - except subprocess.CalledProcessError as e: - if attempt == max_retries - 1: - raise PluginInstallError(f'插件 {plugin} 依赖安装失败:{e}') from e - continue - except subprocess.CalledProcessError as e: - raise PluginInstallError(f'插件 {plugin} 依赖安装失败:{e}') from e + pip_install = ['uv', 'pip', 'install', '-r', requirements_file] + if not _is_in_virtualenv(): + pip_install.append('--system') + if settings.PLUGIN_PIP_CHINA: + pip_install.extend(['-i', settings.PLUGIN_PIP_INDEX_URL]) + + max_retries = settings.PLUGIN_PIP_MAX_RETRY + for attempt in range(max_retries): + try: + subprocess.check_call(pip_install) + break + except subprocess.TimeoutExpired: + if attempt == max_retries - 1: + raise PluginInstallError(f'插件 {plugin} 依赖安装超时') + continue + except subprocess.CalledProcessError as e: + if attempt == max_retries - 1: + raise PluginInstallError(f'插件 {plugin} 依赖安装失败:{e}') from e + continue def uninstall_requirements(plugin: str) -> None: @@ -95,7 +78,7 @@ def uninstall_requirements(plugin: str) -> None: requirements_file = PLUGIN_DIR / plugin / 'requirements.txt' if os.path.exists(requirements_file): try: - pip_uninstall = ['uv', 'pip', 'uninstall', '-r', str(requirements_file), '-y'] + pip_uninstall = ['uv', 'pip', 'uninstall', '-r', str(requirements_file)] if not _is_in_virtualenv(): pip_uninstall.append('--system') subprocess.check_call(pip_uninstall, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) diff --git a/backend/run.py b/backend/run.py index f3fbd9c..3f98f86 100644 --- a/backend/run.py +++ b/backend/run.py @@ -1,10 +1,8 @@ -import os +import granian -import uvicorn +from backend.cli import CustomReloadFilter if __name__ == '__main__': - # 为什么独立此启动文件:https://stackoverflow.com/questions/64003384 - # DEBUG: # 如果你喜欢在 IDE 中进行 DEBUG,可在 IDE 中直接右键启动此文件 # 如果你喜欢通过 print 方式进行调试,建议使用 fba cli 方式启动服务 @@ -13,10 +11,11 @@ # 如果你正在通过 python 命令启动此文件,请遵循以下事宜: # 1. 按照官方文档通过 uv 安装依赖 # 2. 命令行空间位于 backend 目录下 - uvicorn.run( - app='backend.main:app', - host='127.0.0.1', + granian.Granian( + target='main:app', + interface='asgi', + address='127.0.0.1', port=8000, reload=True, - reload_excludes=[os.path.abspath('../.venv')], - ) + reload_filter=CustomReloadFilter, + ).serve() diff --git a/backend/scripts/format.sh b/backend/scripts/format.sh index 816bab2..7741d5f 100644 --- a/backend/scripts/format.sh +++ b/backend/scripts/format.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -ruff format --check +ruff format --preview diff --git a/backend/sql/mysql/init_snowflake_test_data.sql b/backend/sql/mysql/init_snowflake_test_data.sql index d1f0c40..d98a12f 100644 --- a/backend/sql/mysql/init_snowflake_test_data.sql +++ b/backend/sql/mysql/init_snowflake_test_data.sql @@ -1,4 +1,4 @@ insert into sys_user (id, uuid, username, nickname, password, salt, email, status, is_superuser, is_staff, is_multi_login, avatar, phone, join_time, last_login_time, last_password_changed_time, created_time, updated_time) values -(2048601263834267648, uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', unhex('24326224313224387932654E7563583139566A6D5A33745968424C634F'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), now(), now(), null), -(2049946297615646720, uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', unhex('24326224313224424D6958734E514167547837614E63376B56676E7765'), 'test@example.com', 1, false, false, false, null, null, now(), now(), now(), now(), null); +(2048601263834267648, uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', unhex('24326224313224387932654E7563583139566A6D5A33745968424C634F'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), now(), now(), null), +(2049946297615646720, uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', unhex('24326224313224424D6958734E514167547837614E63376B56676E7765'), 'test@example.com', 1, false, false, false, null, null, now(), now(), now(), now(), null); diff --git a/backend/sql/mysql/init_test_data.sql b/backend/sql/mysql/init_test_data.sql index 860258c..b464bd6 100644 --- a/backend/sql/mysql/init_test_data.sql +++ b/backend/sql/mysql/init_test_data.sql @@ -1,4 +1,4 @@ insert into sys_user (id, uuid, username, nickname, password, salt, email, status, is_superuser, is_staff, is_multi_login, avatar, phone, join_time, last_login_time, last_password_changed_time, created_time, updated_time) values -(1, uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', unhex('24326224313224387932654E7563583139566A6D5A33745968424C634F'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), now(), now(), null), +(1, uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', unhex('24326224313224387932654E7563583139566A6D5A33745968424C634F'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), now(), now(), null), (2, uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', unhex('24326224313224424D6958734E514167547837614E63376B56676E7765'), 'test@example.com', 1, false, false, false, null, null, now(), now(), now(), now(), null); diff --git a/backend/sql/postgresql/init_snowflake_test_data.sql b/backend/sql/postgresql/init_snowflake_test_data.sql index bce8198..b4f8b1d 100644 --- a/backend/sql/postgresql/init_snowflake_test_data.sql +++ b/backend/sql/postgresql/init_snowflake_test_data.sql @@ -1,4 +1,4 @@ insert into sys_user (id, uuid, username, nickname, password, salt, email, status, is_superuser, is_staff, is_multi_login, avatar, phone, join_time, last_login_time, last_password_changed_time, created_time, updated_time) values -(2048601269672738816, gen_random_uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', decode('24326224313224387932654E7563583139566A6D5A33745968424C634F', 'hex'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), now(), now(), null), -(2049946297615646720, gen_random_uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', decode('24326224313224424D6958734E514167547837614E63376B56676E7765', 'hex'), 'test@example.com', 1, false, false, false, null, null, now(), now(), now(), now(), null); +(2048601269672738816, gen_random_uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', decode('24326224313224387932654E7563583139566A6D5A33745968424C634F', 'hex'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), now(), now(), null), +(2049946297615646720, gen_random_uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', decode('24326224313224424D6958734E514167547837614E63376B56676E7765', 'hex'), 'test@example.com', 1, false, false, false, null, null, now(), now(), now(), now(), null); diff --git a/backend/sql/postgresql/init_test_data.sql b/backend/sql/postgresql/init_test_data.sql index 3dce563..6958e72 100644 --- a/backend/sql/postgresql/init_test_data.sql +++ b/backend/sql/postgresql/init_test_data.sql @@ -3,5 +3,4 @@ values (1, gen_random_uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', decode('24326224313224387932654E7563583139566A6D5A33745968424C634F', 'hex'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), now(), now(), null), (2, gen_random_uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', decode('24326224313224424D6958734E514167547837614E63376B56676E7765', 'hex'), 'test@example.com', 1, false, false, false, null, null, now(), now(), now(), now(), null); - select setval(pg_get_serial_sequence('sys_user', 'id'),coalesce(max(id), 0) + 1, true) from sys_user; diff --git a/backend/utils/build_tree.py b/backend/utils/build_tree.py deleted file mode 100644 index eb37383..0000000 --- a/backend/utils/build_tree.py +++ /dev/null @@ -1,132 +0,0 @@ -import operator - -from collections.abc import Sequence -from typing import Any - -from backend.common.enums import BuildTreeType -from backend.utils.serializers import RowData, select_list_serialize - - -def get_tree_nodes(row: Sequence[RowData], *, is_sort: bool, sort_key: str) -> list[dict[str, Any]]: - """ - 获取所有树形结构节点 - - :param row: 原始数据行序列 - :param is_sort: 是否启用结果排序 - :param sort_key: 基于此键对结果进行进行排序 - :return: - """ - tree_nodes = select_list_serialize(row) - if is_sort: - tree_nodes.sort(key=operator.itemgetter(sort_key)) - return tree_nodes - - -def traversal_to_tree(nodes: list[dict[str, Any]]) -> list[dict[str, Any]]: - """ - 通过遍历算法构造树形结构 - - :param nodes: 树节点列表 - :return: - """ - tree: list[dict[str, Any]] = [] - node_dict = {node['id']: node for node in nodes} - - for node in nodes: - parent_id = node['parent_id'] - if parent_id is None: - tree.append(node) - else: - parent_node = node_dict.get(parent_id) - if parent_node is not None: - if 'children' not in parent_node: - parent_node['children'] = [] - if node not in parent_node['children']: - parent_node['children'].append(node) - else: - if node not in tree: - tree.append(node) - - return tree - - -def recursive_to_tree(nodes: list[dict[str, Any]], *, parent_id: int | None = None) -> list[dict[str, Any]]: - """ - 通过递归算法构造树形结构(性能影响较大) - - :param nodes: 树节点列表 - :param parent_id: 父节点 ID,默认为 None 表示根节点 - :return: - """ - tree: list[dict[str, Any]] = [] - for node in nodes: - if node['parent_id'] == parent_id: - child_nodes = recursive_to_tree(nodes, parent_id=node['id']) - if child_nodes: - node['children'] = child_nodes - tree.append(node) - return tree - - -def get_tree_data( - row: Sequence[RowData], - build_type: BuildTreeType = BuildTreeType.traversal, - *, - parent_id: int | None = None, - is_sort: bool = True, - sort_key: str = 'sort', -) -> list[dict[str, Any]]: - """ - 获取树形结构数据 - - :param row: 原始数据行序列 - :param build_type: 构建树形结构的算法类型,默认为遍历算法 - :param parent_id: 父节点 ID,仅在递归算法中使用 - :param is_sort: 是否启用结果排序 - :param sort_key: 基于此键对结果进行进行排序 - :return: - """ - nodes = get_tree_nodes(row, is_sort=is_sort, sort_key=sort_key) - match build_type: - case BuildTreeType.traversal: - tree = traversal_to_tree(nodes) - case BuildTreeType.recursive: - tree = recursive_to_tree(nodes, parent_id=parent_id) - case _: - raise ValueError(f'无效的算法类型:{build_type}') - return tree - - -def get_vben5_tree_data( - row: Sequence[RowData], - *, - is_sort: bool = True, - sort_key: str = 'sort', -) -> list[dict[str, Any]]: - """ - 获取 vben5 菜单树形结构数据 - - :param row: 原始数据行序列 - :param is_sort: 是否启用结果排序 - :param sort_key: 基于此键对结果进行进行排序 - :return: - """ - meta_keys = {'title', 'icon', 'link', 'cache', 'display', 'status'} - - vben5_nodes = [ - { - **{k: v for k, v in node.items() if k not in meta_keys}, - 'meta': { - 'title': node['title'], - 'icon': node['icon'], - 'iframeSrc': node['link'] if node['type'] == 3 else '', - 'link': node['link'] if node['type'] == 4 else '', - 'keepAlive': node['cache'], - 'hideInMenu': not bool(node['display']), - 'menuVisibleWithForbidden': not bool(node['status']), - }, - } - for node in get_tree_nodes(row, is_sort=is_sort, sort_key=sort_key) - ] - - return traversal_to_tree(vben5_nodes) diff --git a/backend/utils/console.py b/backend/utils/console.py index ef3ad7d..240d784 100644 --- a/backend/utils/console.py +++ b/backend/utils/console.py @@ -1,3 +1,28 @@ -from rich import get_console +from rich.console import Console -console = get_console() + +class CustomConsole(Console): + """自定义控制台""" + + def note(self, msg: str) -> None: + """输出注释""" + self.print(f'[bold white]•[/] [white]{msg}[/]') + + def info(self, msg: str) -> None: + """输出信息""" + self.print(f'[bold cyan]•[/] {msg}') + + def tip(self, msg: str) -> None: + """输出提示消息""" + self.print(f'[bold green]✓[/] [green]{msg}[/]') + + def warning(self, msg: str) -> None: + """输出警告消息""" + self.print(f'[bold yellow]⚠[/] [yellow]{msg}[/]') + + def caution(self, msg: str) -> None: + """输出危险消息""" + self.print(f'[bold red]✗[/] [red]{msg}[/]') + + +console = CustomConsole() diff --git a/backend/utils/dynamic_config.py b/backend/utils/dynamic_config.py index 1f1991a..eefd861 100644 --- a/backend/utils/dynamic_config.py +++ b/backend/utils/dynamic_config.py @@ -5,8 +5,8 @@ from backend.core.conf import settings from backend.database.db import async_engine -from backend.plugin.config.crud.crud_config import config_dao from backend.plugin.config.enums import ConfigType +from backend.plugin.config.service.config_service import config_service from backend.utils.serializers import select_list_serialize _sys_config_table_exists: bool | None = None @@ -16,7 +16,7 @@ async def check_sys_config_table_exists() -> bool: """检查 sys_config 表是否存在""" global _sys_config_table_exists if _sys_config_table_exists is None: - async with async_engine.begin() as conn: + async with async_engine.connect() as conn: _sys_config_table_exists = await conn.run_sync(lambda c: inspect(c).has_table('sys_config', schema=None)) return _sys_config_table_exists @@ -44,11 +44,12 @@ async def _load_config( if not await check_sys_config_table_exists(): return - dynamic_config = await config_dao.get_all(db, config_type) + dynamic_config = await config_service.get_all(db=db, type=config_type) if not dynamic_config: return - configs = {dc['key']: dc['value'] for dc in select_list_serialize(dynamic_config)} + config_list = select_list_serialize(dynamic_config) if hasattr(dynamic_config[0], '__table__') else dynamic_config + configs = {dc['key']: dc['value'] for dc in config_list} if configs.get(status_key, '1') == '0': return @@ -57,6 +58,26 @@ async def _load_config( setattr(settings, config_key, converter(configs[config_key])) +async def load_user_security_config(db: AsyncSession) -> None: + """ + 获取用户安全配置 + + :param db: 数据库会话 + :return: + """ + mapping = { + 'USER_LOCK_THRESHOLD': int, + 'USER_LOCK_SECONDS': int, + 'USER_PASSWORD_EXPIRY_DAYS': int, + 'USER_PASSWORD_REMINDER_DAYS': int, + 'USER_PASSWORD_HISTORY_CHECK_COUNT': int, + 'USER_PASSWORD_MIN_LENGTH': int, + 'USER_PASSWORD_MAX_LENGTH': int, + 'USER_PASSWORD_REQUIRE_SPECIAL_CHAR': _to_bool, + } + await _load_config(db, ConfigType.user_security, mapping, 'USER_SECURITY_CONFIG_STATUS') + + async def load_login_config(db: AsyncSession) -> None: """ 获取登录配置 @@ -68,3 +89,20 @@ async def load_login_config(db: AsyncSession) -> None: 'LOGIN_CAPTCHA_ENABLED': _to_bool, } await _load_config(db, ConfigType.login, mapping, 'LOGIN_CONFIG_STATUS') + + +async def load_email_config(db: AsyncSession) -> None: + """ + 获取邮箱配置 + + :param db: 数据库会话 + :return: + """ + mapping = { + 'EMAIL_HOST': str, + 'EMAIL_PORT': int, + 'EMAIL_SSL': _to_bool, + 'EMAIL_USERNAME': str, + 'EMAIL_PASSWORD': str, + } + await _load_config(db, ConfigType.email, mapping, 'EMAIL_CONFIG_STATUS') diff --git a/backend/utils/dynamic_import.py b/backend/utils/dynamic_import.py index 08aee91..e888509 100644 --- a/backend/utils/dynamic_import.py +++ b/backend/utils/dynamic_import.py @@ -13,7 +13,7 @@ T = TypeVar('T') -@lru_cache(maxsize=512) +@lru_cache(maxsize=128) def import_module_cached(module_path: str) -> Any: """ 缓存导入模块 @@ -84,9 +84,9 @@ def get_app_models() -> list[object]: return objs -@lru_cache -def get_all_models() -> list[object]: +@lru_cache(256) +def get_all_models() -> tuple[object, ...]: """获取所有模型类""" from backend.plugin.core import get_plugin_models - return get_app_models() + get_plugin_models() + return tuple(get_app_models() + get_plugin_models()) diff --git a/backend/utils/file_ops.py b/backend/utils/file_ops.py index a48b629..bc3405a 100644 --- a/backend/utils/file_ops.py +++ b/backend/utils/file_ops.py @@ -1,7 +1,6 @@ from anyio import open_file from fastapi import UploadFile -from backend.common.enums import FileType from backend.common.exception import errors from backend.common.log import log from backend.core.conf import settings @@ -35,16 +34,14 @@ def upload_file_verify(file: UploadFile) -> None: if not file_ext: raise errors.RequestError(msg='未知的文件类型') - if file_ext == FileType.image: - if file_ext not in settings.UPLOAD_IMAGE_EXT_INCLUDE: - raise errors.RequestError(msg='此图片格式暂不支持') + if file_ext in settings.UPLOAD_IMAGE_EXT_INCLUDE: if file.size > settings.UPLOAD_IMAGE_SIZE_MAX: raise errors.RequestError(msg='图片超出最大限制,请重新选择') - elif file_ext == FileType.video: - if file_ext not in settings.UPLOAD_VIDEO_EXT_INCLUDE: - raise errors.RequestError(msg='此视频格式暂不支持') + elif file_ext in settings.UPLOAD_VIDEO_EXT_INCLUDE: if file.size > settings.UPLOAD_VIDEO_SIZE_MAX: raise errors.RequestError(msg='视频超出最大限制,请重新选择') + else: + raise errors.RequestError(msg=f'此文件格式 {file_ext} 暂不支持') async def upload_file(file: UploadFile) -> str: diff --git a/backend/utils/format.py b/backend/utils/format.py index 5c2fb72..662cbad 100644 --- a/backend/utils/format.py +++ b/backend/utils/format.py @@ -2,17 +2,15 @@ def fmt_seconds(seconds: int) -> str: """格式化秒数为可读的时间字符串""" days, rem = divmod(int(seconds), 86400) hours, rem = divmod(rem, 3600) - minutes, secs = divmod(rem, 60) + minutes, _ = divmod(rem, 60) parts = [] if days: - parts.append(f'{days} 天') + parts.append(f'{days} days') if hours: - parts.append(f'{hours} 小时') + parts.append(f'{hours} hours') if minutes: - parts.append(f'{minutes} 分钟') - if secs: - parts.append(f'{secs} 秒') - return ' '.join(parts) if parts else '0 秒' + parts.append(f'{minutes} minutes') + return ' '.join(parts) if parts else '0 seconds' def fmt_bytes(size: float) -> str: diff --git a/backend/utils/limiter.py b/backend/utils/limiter.py index 743c900..f98ddb8 100644 --- a/backend/utils/limiter.py +++ b/backend/utils/limiter.py @@ -1,23 +1,97 @@ +from collections.abc import Awaitable, Callable from math import ceil from fastapi import Request, Response +from fastapi_pagination.utils import is_async_callable +from pyrate_limiter import AbstractBucket, Limiter, Rate +from pyrate_limiter.buckets import RedisBucket +from starlette.concurrency import run_in_threadpool from backend.common.exception import errors from backend.common.response.response_code import StandardResponseCode +from backend.core.conf import settings +from backend.database.redis import redis_client +from backend.utils.request_parse import get_request_ip +IdentifierCallable = Callable[[Request], str] | Callable[[Request], Awaitable[str]] +CallbackCallable = Callable[[Request, Response, int], None] | Callable[[Request, Response, int], Awaitable[None]] -async def http_limit_callback(request: Request, response: Response, expire: int) -> None: # noqa: RUF029 + +def default_identifier(request: Request) -> str: + """ + 默认标识符 + + :param request: FastAPI 请求对象 + :return: + """ + ip = get_request_ip(request) + return f'{ip}:{request.scope["path"]}' + + +def default_callback(request: Request, response: Response, retry_after: int) -> None: """ - 请求限制时的默认回调函数 + 默认回调 :param request: FastAPI 请求对象 :param response: FastAPI 响应对象 - :param expire: 剩余毫秒数 + :param retry_after: 下次重试秒数 :return: """ - expires = ceil(expire / 1000) raise errors.HTTPError( code=StandardResponseCode.HTTP_429, msg='请求过于频繁,请稍后重试', - headers={'Retry-After': str(expires)}, + headers={'Retry-After': str(retry_after)}, ) + + +class RateLimiter: + """速率限制器""" + + def __init__( + self, + *rates: Rate, + identifier: IdentifierCallable = default_identifier, + bucket: AbstractBucket | None = None, + limiter: Limiter | None = None, + callback: CallbackCallable = default_callback, + ) -> None: + """ + 初始化速率限制器 + + :param rates: pyrate_limiter Rate 对象,支持传入单个或多个 + :param identifier: 自定义标识符函数 + :param bucket: pyrate_limiter AbstractBucket 实例 + :param limiter: pyrate_limiter Limiter 实例 + :param callback: 自定义限流回调函数 + :return: + """ + if not rates and bucket is None: + raise errors.ServerError(msg='至少需要传入一个 Rate 或 bucket 实例') + self.rates = list(rates) + self.identifier = identifier + self.bucket = bucket + self.limiter = limiter + self.callback = callback + + async def __call__(self, request: Request, response: Response) -> None: + if self.limiter is None: + if self.bucket is None: + self.bucket = await RedisBucket.init( # type: ignore + rates=self.rates, + redis=redis_client, + bucket_key=f'{settings.REQUEST_LIMITER_REDIS_PREFIX}', + ) + self.limiter = Limiter(self.bucket) + + if is_async_callable(self.identifier): + identifier = await self.identifier(request) + else: + identifier = await run_in_threadpool(self.identifier, request) + + acquired = await self.limiter.try_acquire_async(identifier, blocking=False) + if not acquired: + retry_after = ceil(self.bucket.failing_rate.interval / 1000) + if is_async_callable(self.callback): + await self.callback(request, response, retry_after) + else: + await run_in_threadpool(self.callback, request, response, retry_after) diff --git a/backend/utils/otel.py b/backend/utils/otel.py deleted file mode 100644 index 88840fb..0000000 --- a/backend/utils/otel.py +++ /dev/null @@ -1,102 +0,0 @@ -from fastapi import FastAPI -from opentelemetry import _logs, metrics, trace -from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter -from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter -from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter -from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor -from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor -from opentelemetry.instrumentation.logging import LoggingInstrumentor -from opentelemetry.instrumentation.redis import RedisInstrumentor -from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor -from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler -from opentelemetry.sdk._logs._internal.export import BatchLogRecordProcessor -from opentelemetry.sdk.metrics import MeterProvider -from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader -from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor - -from backend.common.log import log, request_id_filter -from backend.core.conf import settings -from backend.database.db import async_engine -from backend.database.redis import redis_client - - -def _init_tracer(resource: Resource) -> None: - """ - 初始化追踪器 - - :param resource: 遥测资源 - :return: - """ - tracer_provider = TracerProvider(resource=resource) - span_exporter = OTLPSpanExporter(endpoint=settings.GRAFANA_OTLP_GRPC_ENDPOINT, insecure=True) - - tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter)) - trace.set_tracer_provider(tracer_provider) - - -def _init_metrics(resource: Resource) -> None: - """ - 初始化指标 - - :param resource: 遥测资源 - :return: - """ - metric_exporter = OTLPMetricExporter(endpoint=settings.GRAFANA_OTLP_GRPC_ENDPOINT, insecure=True) - meter_provider = MeterProvider( - resource=resource, - metric_readers=[PeriodicExportingMetricReader(metric_exporter)], - ) - - metrics.set_meter_provider(meter_provider) - - -def _init_logging(resource: Resource) -> None: - """ - 初始化日志 - - :param resource: 遥测资源 - :return: - """ - logger_provider = LoggerProvider(resource=resource) - logger_exporter = OTLPLogExporter(endpoint=settings.GRAFANA_OTLP_GRPC_ENDPOINT, insecure=True) - - logger_provider.add_log_record_processor(BatchLogRecordProcessor(logger_exporter)) - _logs.set_logger_provider(logger_provider) - - otel_logging_handler = LoggingHandler(logger_provider=logger_provider) - log.add( # type: ignore - otel_logging_handler, - level=settings.LOG_STD_LEVEL, - format=settings.LOG_FORMAT, - filter=lambda record: request_id_filter(record), - ) - - -def init_otel(app: FastAPI) -> None: - """ - 初始化 OpenTelemetry - - :param app: FastAPI 应用实例 - :return: - """ - from backend import __version__ - - resource = Resource( - attributes={ - 'service.name': settings.GRAFANA_APP_NAME, - 'service.version': __version__, - 'deployment.environment': settings.ENVIRONMENT, - }, - ) - - _init_tracer(resource) - # _init_metrics(resource) - _init_logging(resource) - - LoggingInstrumentor().instrument(set_logging_format=True) - SQLAlchemyInstrumentor().instrument(engine=async_engine.sync_engine) - RedisInstrumentor.instrument_client(redis_client) # type: ignore - HTTPXClientInstrumentor().instrument() - FastAPIInstrumentor.instrument_app(app) diff --git a/backend/utils/pattern_validate.py b/backend/utils/pattern_validate.py index f72d7c7..06e9025 100644 --- a/backend/utils/pattern_validate.py +++ b/backend/utils/pattern_validate.py @@ -36,12 +36,12 @@ def is_phone(number: str) -> re.Match[str]: def is_git_url(url: str) -> re.Match[str]: """ - 检查 git URL 格式 + 检查 git URL 格式(仅允许 HTTP/HTTPS 协议) :param url: 待检查的 URL :return: """ - git_pattern = r'^(?!(git\+ssh|ssh)://|git@)(?Pgit|https?|file)://(?P[^/]*)(?P(?:/[^/]*)*/)(?P[^/]+?)(?:\.git)?$' + git_pattern = r'^(?Phttps?)://(?P[^/]*)(?P(?:/[^/]*)*/)(?P[^/]+?)(?:\.git)?$' return match_string(git_pattern, url) @@ -76,3 +76,14 @@ def is_has_special_char(value: str) -> re.Match[str]: """ special_char_pattern = r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]' return search_string(special_char_pattern, value) + + +def is_english_identifier(value: str) -> re.Match[str]: + """ + 检查英文标识符 + + :param value: 待检查的值 + :return: + """ + identifier_pattern = r'^[a-zA-Z][a-zA-Z_]*$' + return match_string(identifier_pattern, value) diff --git a/backend/utils/request_parse.py b/backend/utils/request_parse.py index 81b295e..1d68bec 100644 --- a/backend/utils/request_parse.py +++ b/backend/utils/request_parse.py @@ -29,6 +29,7 @@ def get_request_ip(request: Request) -> str: # 忽略 pytest if request.client.host == 'testclient': return '127.0.0.1' + return request.client.host diff --git a/backend/utils/serializers.py b/backend/utils/serializers.py index 20bcb65..d539deb 100644 --- a/backend/utils/serializers.py +++ b/backend/utils/serializers.py @@ -9,6 +9,8 @@ from sqlalchemy.orm import ColumnProperty, SynonymProperty, class_mapper from starlette.responses import JSONResponse +from backend.common.log import log + RowData = Row[Any] | RowMapping | Any R = TypeVar('R', bound=RowData) @@ -79,68 +81,74 @@ def select_join_serialize( # noqa: C901 return_as_dict: bool = False, ) -> dict[str, Any] | list[dict[str, Any]] | tuple[Any, ...] | list[tuple[Any, ...]] | None: """ - 将 SQLAlchemy 连接查询结果序列化为字典或支持属性访问的 namedtuple - - 扁平序列化:``relationships=None`` - | 将所有查询结果平铺到同一层级,不进行嵌套处理 - 输出:Result(name='Alice', dept=Dept(...)) - - 嵌套序列化:``relationships=['User-m2o-Dept', 'User-m2m-Role:permissions', 'Role-m2m-Menu']`` - | 根据指定的关系类型将数据嵌套组织,支持层级结构 - | row = select(User, Dept, Role).join(...).all() - 输出:Result(name='Alice', dept=Dept(...), permissions=[Role(..., menus=[Menu(...)])]) + 将 SQLAlchemy 连接查询结果序列化为字典或 namedtuple - :param row: SQLAlchemy 查询结果 - :param relationships: 表之间的虚拟关系 + 扁平序列化(relationships=None): + 所有结果平铺到同一层级,不嵌套 + 例:Result(name='Alice', dept=Dept(...)) - source_model_class-type-target_model_class[:custom_name], type: o2m/m2o/o2o/m2m + 嵌套序列化: + 根据关系类型嵌套组织数据,支持层级结构 + 例:relationships=['User-m2o-Dept', 'User-m2m-Role:permissions'] + 输出:Result(name='Alice', dept=Dept(...), permissions=[Role(...)]) - - o2m (一对多): 目标模型类名会自动添加's'变为复数形式 (如: dept->depts) - - m2o (多对一): 目标模型类名保持单数形式 (如: user->user) - - o2o (一对一): 目标模型类名保持单数形式 (如: profile->profile) - - m2m (多对多): 目标模型类名会自动添加's'变为复数形式 (如: role->roles) - - 自定义名称: 可以通过在关系字符串末尾添加 ':custom_name' 来指定自定义的目标字段名 - 例如: 'User-m2m-Role:permissions' 会将角色数据放在 'permissions' 字段而不是默认的 'roles' + 关系格式:source_model-type-target_model[:custom_name] + - type: o2m(一对多), m2o(多对一), o2o(一对一), m2m(多对多) + - o2m/m2m: 目标字段名自动加 's' 复数化 + - m2o/o2o: 目标字段名保持单数 + - custom_name: 自定义目标字段名 - :param return_as_dict: False 返回 namedtuple,True 返回 dict + :param row: SQLAlchemy 查询结果 + :param relationships: 关系定义列表 + :param return_as_dict: True 返回字典,False 返回 namedtuple :return: """ + list_relationship_types = {'o2m', 'm2m'} + all_relationship_types = {'o2m', 'm2o', 'o2o', 'm2m'} + + def get_obj_id(target_obj: Any) -> int | str: + return getattr(target_obj, 'id', None) or id(target_obj) - def get_relation_key(model_name: str, rel_type: str, custom_field: str | None = None) -> str: - """获取关系键名""" - return custom_field or (model_name if rel_type in ('o2o', 'm2o') else f'{model_name}s') + def extract_row_elements(row_data: Any) -> tuple: + return row_data if hasattr(row_data, '__getitem__') else (row_data,) + + def get_relationship_key(model: str, relationship_type: str, custom_field: str | None) -> str: + return custom_field or (model if relationship_type not in list_relationship_types else f'{model}s') def parse_relationships(relationship_list: list[str]) -> tuple[dict, dict, dict]: - """解析关系定义""" if not relationship_list: return {}, {}, {} - parsed_relation_graph = defaultdict(dict) - parsed_reverse_relation = {} - parsed_custom_names = {} + graph = defaultdict(dict) + reverse = {} + customs = {} for rel_str in relationship_list: parts = rel_str.split(':', 1) rel_part = parts[0].strip() - field_custom_name = parts[1].strip() if len(parts) > 1 else None + custom_name = parts[1].strip() if len(parts) > 1 else None - rel_info = rel_part.split('-') - if len(rel_info) != 3: + info = rel_part.split('-') + if len(info) != 3: + log.warning(f'Invalid relationship: "{rel_str}", expected "source-type-target[:custom]"') continue - source_model, rel_type, target_model = (info.lower() for info in rel_info) - if rel_type not in ('o2m', 'm2o', 'o2o', 'm2m'): + src, parsed_type, dst = (x.lower() for x in info) + if parsed_type not in all_relationship_types: + log.warning( + f'Invalid relationship type: "{parsed_type}" in "{rel_str}", ' + f'must be one of: {", ".join(all_relationship_types)}' + ) continue - parsed_relation_graph[source_model][target_model] = rel_type - parsed_reverse_relation[target_model] = source_model - if field_custom_name: - parsed_custom_names[source_model, target_model] = field_custom_name + graph[src][dst] = parsed_type + reverse[dst] = src + if custom_name: + customs[src, dst] = custom_name - return parsed_relation_graph, parsed_reverse_relation, parsed_custom_names + return graph, reverse, customs def get_model_columns(model_obj: Any) -> list[str]: - """获取模型列名""" mapper = class_mapper(type(model_obj)) return [ prop.key @@ -148,17 +156,25 @@ def get_model_columns(model_obj: Any) -> list[str]: if isinstance(prop, (ColumnProperty, SynonymProperty)) and hasattr(model_obj, prop.key) ] - def get_unique_objects(objs: list[Any], key_attr: str = 'id') -> list[Any]: - """根据键属性去重对象列表""" + def dedupe_objects(obj_list: list[Any]) -> list[Any]: seen = set() unique = [] - for item in objs: - item_id = getattr(item, key_attr, None) + for item in obj_list: + item_id = getattr(item, 'id', None) if item_id is not None and item_id not in seen: seen.add(item_id) unique.append(item) return unique + def build_namedtuple(name: str, data: dict) -> Any: + if return_as_dict or name not in namedtuple_cache: + return None + for field in namedtuple_cache[name]._fields: + if field not in data: + data[field] = None + return namedtuple_cache[name](**data) + + # 输入验证 if not row: return None @@ -166,211 +182,207 @@ def get_unique_objects(objs: list[Any], key_attr: str = 'id') -> list[Any]: if not rows_list: return None - # 获取主对象信息 - first_row = rows_list[0] - main_obj = first_row[0] if hasattr(first_row, '__getitem__') and first_row else first_row - if main_obj is None: + # 主对象信息 + first_row = extract_row_elements(rows_list[0]) + primary_obj = first_row[0] + if primary_obj is None: return None - main_obj_name = type(main_obj).__name__.lower() - main_columns = get_model_columns(main_obj) + primary_obj_name = type(primary_obj).__name__.lower() + primary_columns = get_model_columns(primary_obj) - # 解析关系 + # 关系解析 relation_graph, reverse_relation, custom_names = parse_relationships(relationships or []) has_relationships = bool(relation_graph) - # 预处理所有模型类型和列信息 + # 预处理模型信息 model_info = {} - cls_idxs = {} + cls_idx = {} - for preprocess_row in rows_list: - preprocess_row_items = preprocess_row if hasattr(preprocess_row, '__getitem__') else (preprocess_row,) - for idx, row_obj in enumerate(preprocess_row_items): - if row_obj is None: + for row_item in rows_list: + row_elements = extract_row_elements(row_item) + for idx, element in enumerate(row_elements): + if element is None: continue - obj_class_name = type(row_obj).__name__.lower() - if obj_class_name not in model_info: - model_info[obj_class_name] = get_model_columns(row_obj) - if obj_class_name not in cls_idxs: - cls_idxs[obj_class_name] = idx - - # 数据收集和分组 - main_data = {} - grouped_data = defaultdict(lambda: defaultdict(list)) - - for data_row in rows_list: - data_row_items = data_row if hasattr(data_row, '__getitem__') else (data_row,) - if not data_row_items or data_row_items[0] is None: + element_cls = type(element).__name__.lower() + if element_cls not in model_info: + model_info[element_cls] = get_model_columns(element) + if element_cls not in cls_idx: + cls_idx[element_cls] = idx + + # 数据分组 + main_objects = {} + children_objects = defaultdict(lambda: defaultdict(list)) + + for row_item in rows_list: + row_elements = extract_row_elements(row_item) + if not row_elements or row_elements[0] is None: continue - main_obj = data_row_items[0] - main_id = getattr(main_obj, 'id', None) or id(main_obj) + main_obj = row_elements[0] + main_id = get_obj_id(main_obj) - if main_id not in main_data: - main_data[main_id] = main_obj + if main_id not in main_objects: + main_objects[main_id] = main_obj - # 收集子对象 - for child_obj in data_row_items[1:]: + for child_obj in row_elements[1:]: if child_obj is None: continue - child_class_name = type(child_obj).__name__.lower() - grouped_data[main_id][child_class_name].append(child_obj) + child_type = type(child_obj).__name__.lower() + children_objects[main_id][child_type].append(child_obj) - if not main_data: + if not main_objects: return None - # 预生成 namedtuple 类型 + # namedtuple 类型预生成 namedtuple_cache = {} if not return_as_dict: - for cls_name, columns in model_info.items(): - if columns: - # 为嵌套关系预计算完整字段列表 - full_columns = columns.copy() - if has_relationships: - for target_class, relation_type in relation_graph.get(cls_name, {}).items(): - field_name = custom_names.get((cls_name, target_class)) - rel_key = get_relation_key(target_class, relation_type, field_name) - full_columns.append(rel_key) - full_columns = sorted(set(full_columns)) # 去重并排序 - - namedtuple_cache[cls_name] = namedtuple(cls_name.capitalize(), full_columns or columns) # noqa: PYI024 - - def build_flat_result(build_main_id: int, build_main_obj: Any) -> dict[str, Any]: # noqa: C901 - """构建扁平化结果""" - flat_result = {col: getattr(build_main_obj, col, None) for col in main_columns} - - for class_name in sorted(grouped_data[build_main_id]): - if class_name == main_obj_name: + for model_name, model_columns in model_info.items(): + if not model_columns: continue - flat_objs = get_unique_objects(grouped_data[build_main_id][class_name]) - cls_columns = model_info.get(class_name, []) - - if not flat_objs: - flat_result[class_name] = [] - elif len(flat_objs) == 1: - obj_data = {col: getattr(flat_objs[0], col, None) for col in cls_columns} - # 确保 namedtuple 所需的所有字段都存在 - if not return_as_dict and class_name in namedtuple_cache: - nt_fields = getattr(namedtuple_cache[class_name], '_fields', []) - for field in nt_fields: - if field not in obj_data: - obj_data[field] = None - flat_result[class_name] = obj_data if return_as_dict else namedtuple_cache[class_name](**obj_data) + field_list = model_columns.copy() + if has_relationships: + for target, target_rtype in relation_graph.get(model_name, {}).items(): + nt_key = get_relationship_key(target, target_rtype, custom_names.get((model_name, target))) + field_list.append(nt_key) + field_list = list(dict.fromkeys(field_list)) + + namedtuple_cache[model_name] = namedtuple(model_name.capitalize(), field_list) # noqa: PYI024 + + # 嵌套关系层级结构(一次性构建) + hierarchy = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) + + if has_relationships: + for row_item in rows_list: + row_elements = extract_row_elements(row_item) + if not row_elements or row_elements[0] is None: + continue + + main_id = get_obj_id(row_elements[0]) + m_type_name = type(row_elements[0]).__name__.lower() + + for idx, rel_obj in enumerate(row_elements[1:], 1): # noqa: B007 + if rel_obj is None: + continue + + rel_type_name = type(rel_obj).__name__.lower() + + if rel_type_name in reverse_relation: + parent_type = reverse_relation[rel_type_name] + parent_idx = cls_idx.get(parent_type) + parent = ( + row_elements[parent_idx] if parent_idx is not None and parent_idx < len(row_elements) else None + ) + elif rel_type_name in relation_graph.get(m_type_name, {}): + parent = row_elements[0] + else: + continue + + if parent is None: + continue + + parent_pk = getattr(parent, 'id', None) + if parent_pk is not None: + hierarchy[main_id][rel_type_name][parent_pk].append(rel_obj) + + # 结果构建函数 + def build_flat(target_id: int, target_obj: Any) -> dict[str, Any]: + result = {col: getattr(target_obj, col, None) for col in primary_columns} + + for cls_type in children_objects[target_id]: + if cls_type == primary_obj_name: + continue + + unique_children = dedupe_objects(children_objects[target_id][cls_type]) + child_columns = model_info.get(cls_type, []) + + count = len(unique_children) + field_key = cls_type if count <= 1 else f'{cls_type}s' + + if count == 0: + result[field_key] = [] + elif count == 1: + obj_data = {col: getattr(unique_children[0], col, None) for col in child_columns} + result[field_key] = obj_data if return_as_dict else build_namedtuple(cls_type, obj_data) else: if return_as_dict: - flat_result[class_name] = [ - {col: getattr(flat_obj, col, None) for col in cls_columns} for flat_obj in flat_objs - ] + result[field_key] = [{col: getattr(c, col, None) for col in child_columns} for c in unique_children] else: - nested_result_list = [] - for nested_obj in flat_objs: - obj_data = {col: getattr(nested_obj, col, None) for col in cls_columns} - # 确保 namedtuple 所需的所有字段都存在 - if class_name in namedtuple_cache: - nt_fields = getattr(namedtuple_cache[class_name], '_fields', []) - for field in nt_fields: - if field not in obj_data: - obj_data[field] = None - nested_result_list.append(namedtuple_cache[class_name](**obj_data)) - flat_result[class_name] = nested_result_list - - return flat_result - - def build_nested_result(nested_main_id: int, nested_main_obj: Any) -> dict[str, Any]: # noqa: C901 - """构建嵌套化结果""" - nested_result = {col: getattr(nested_main_obj, col, None) for col in main_columns} - - # 构建关系层级数据结构 - hierarchy = defaultdict(lambda: defaultdict(list)) - for iter_row in rows_list: - iter_row_items = iter_row if hasattr(iter_row, '__getitem__') else (iter_row,) - if not iter_row_items or iter_row_items[0] is None: - continue + result[field_key] = [ + build_namedtuple(cls_type, {col: getattr(c, col, None) for col in child_columns}) + for c in unique_children + ] - iter_main_id = getattr(iter_row_items[0], 'id', None) or id(iter_row_items[0]) - if iter_main_id != nested_main_id: - continue + return result - for _i, related_obj in enumerate(iter_row_items[1:], 1): - if related_obj is None: - continue - related_class_name = type(related_obj).__name__.lower() - - if related_class_name in reverse_relation: - parent_cls = reverse_relation[related_class_name] - parent_idx = cls_idxs.get(parent_cls, 0) - if parent_idx < len(iter_row_items): - parent_obj = iter_row_items[parent_idx] - if parent_obj is not None: - parent_obj_id = getattr(parent_obj, 'id', None) - if parent_obj_id is not None: - hierarchy[related_class_name][parent_obj_id].append(related_obj) - - def build_recursive(current_cls_name: str, current_parent_id: int) -> list: - """递归构建嵌套数据""" - recursive_objs = get_unique_objects(hierarchy[current_cls_name].get(current_parent_id, [])) - if not recursive_objs: + def build_nested(target_id: int, target_obj: Any) -> dict[str, Any]: + result = {col: getattr(target_obj, col, None) for col in primary_columns} + current_hierarchy = hierarchy.get(target_id, defaultdict(lambda: defaultdict(list))) + + def recursive_build(cls_name: str, pk: int) -> list: + nested_dict = current_hierarchy.get(cls_name) + if nested_dict is None: + return [] + objs = dedupe_objects(nested_dict.get(pk, [])) + if not objs: return [] - recursive_result = [] - for nested_obj in recursive_objs: - # 基础数据 - obj_data = {col: getattr(nested_obj, col, None) for col in model_info[current_cls_name]} + output = [] + for item in objs: + item_data = {col: getattr(item, col, None) for col in model_info[cls_name]} - # 处理子关系 - for child_cls, child_rel_type in relation_graph.get(current_cls_name, {}).items(): - child_parent_id = getattr(nested_obj, 'id', None) - if child_parent_id is None: + for sub_type, sub_rel_type in relation_graph.get(cls_name, {}).items(): + sub_pk = getattr(item, 'id', None) + if sub_pk is None: continue - child_list = build_recursive(child_cls, child_parent_id) - child_key = get_relation_key( - child_cls, child_rel_type, custom_names.get((current_cls_name, child_cls)) - ) + sub_list = recursive_build(sub_type, sub_pk) + sub_key = get_relationship_key(sub_type, sub_rel_type, custom_names.get((cls_name, sub_type))) - if child_rel_type in ('m2o', 'o2o'): - obj_data[child_key] = child_list[0] if child_list else None + if sub_rel_type not in list_relationship_types: + item_data[sub_key] = sub_list[0] if sub_list else None else: - obj_data[child_key] = child_list + item_data[sub_key] = sub_list - if not return_as_dict and current_cls_name in namedtuple_cache: - nt_fields = getattr(namedtuple_cache[current_cls_name], '_fields', []) - for field in nt_fields: - if field not in obj_data: - obj_data[field] = None + output.append(item_data if return_as_dict else build_namedtuple(cls_name, item_data)) - recursive_result.append(obj_data if return_as_dict else namedtuple_cache[current_cls_name](**obj_data)) + return output - return recursive_result + for top_type, top_rtype in relation_graph.get(primary_obj_name, {}).items(): + instances = recursive_build(top_type, target_id) + top_key = get_relationship_key(top_type, top_rtype, custom_names.get((primary_obj_name, top_type))) - # 构建顶级关系 - for top_cls_name, top_rel_type in relation_graph.get(main_obj_name, {}).items(): - instances = build_recursive(top_cls_name, nested_main_id) - key = get_relation_key(top_cls_name, top_rel_type, custom_names.get((main_obj_name, top_cls_name))) - - if top_rel_type in ('m2o', 'o2o'): - nested_result[key] = instances[0] if instances else None + if top_rtype not in list_relationship_types: + result[top_key] = instances[0] if instances else None else: - nested_result[key] = instances + result[top_key] = instances - return nested_result + return result - # 构建最终结果 - final_result_list = [] - for current_main_id in sorted(main_data.keys()): - current_main_obj = main_data[current_main_id] + # 最终结果构建 + final_results = [] + processed_ids = set() - if has_relationships: - final_result_data = build_nested_result(current_main_id, current_main_obj) - else: - final_result_data = build_flat_result(current_main_id, current_main_obj) + for row_item in rows_list: + row_elements = extract_row_elements(row_item) + if not row_elements or row_elements[0] is None: + continue + + main_obj = row_elements[0] + main_id = get_obj_id(main_obj) + + if main_id not in main_objects or main_id in processed_ids: + continue + + processed_ids.add(main_id) + + result_data = build_nested(main_id, main_obj) if has_relationships else build_flat(main_id, main_obj) if not return_as_dict: - all_fields = list(final_result_data.keys()) - result_type = namedtuple('Result', all_fields) # noqa: PYI024 - final_result_list.append(result_type(**final_result_data)) + result_type = namedtuple('Result', result_data.keys()) # noqa: PYI024 + final_results.append(result_type(**result_data)) else: - final_result_list.append(final_result_data) + final_results.append(result_data) - return final_result_list[0] if len(final_result_list) == 1 else final_result_list + return final_results[0] if len(final_results) == 1 else final_results diff --git a/backend/utils/sql_parser.py b/backend/utils/sql_parser.py index 82aad2b..e344b9e 100644 --- a/backend/utils/sql_parser.py +++ b/backend/utils/sql_parser.py @@ -5,12 +5,19 @@ from backend.common.exception import errors +# 初始化脚本允许的 SQL 语句前缀 +_INIT_SQL_PREFIXES = frozenset({'select', 'insert', 'set', 'do'}) -async def parse_sql_script(filepath: str) -> list[str]: +# 销毁脚本允许的 SQL 语句前缀 +_DESTROY_SQL_PREFIXES = _INIT_SQL_PREFIXES | {'drop', 'delete', 'alter'} + + +async def parse_sql_script(filepath: str, *, is_destroy: bool = False) -> list[str]: """ 解析 SQL 脚本 :param filepath: 脚本文件路径 + :param is_destroy: 是否为销毁脚本,将允许破坏性操作 :return: """ path = anyio.Path(filepath) @@ -22,9 +29,12 @@ async def parse_sql_script(filepath: str) -> list[str]: while additional_contents := await f.read(1024): contents += additional_contents - statements = split(contents) + statements = [stmt for stmt in split(contents) if stmt.strip()] + allowed_prefixes = _DESTROY_SQL_PREFIXES if is_destroy else _INIT_SQL_PREFIXES for statement in statements: - if not any(statement.lower().startswith(_) for _ in ['select', 'insert']): - raise errors.RequestError(msg='SQL 脚本文件中存在非法操作,仅允许 SELECT 和 INSERT') + if not any(statement.strip().lower().startswith(prefix) for prefix in allowed_prefixes): + raise errors.RequestError( + msg=f'SQL 脚本 {filepath} 存在非法操作,仅允许:{", ".join(item.upper() for item in sorted(allowed_prefixes))}' # noqa: E501 + ) return statements diff --git a/backend/utils/timezone.py b/backend/utils/timezone.py index 30fdbaf..8a387e1 100644 --- a/backend/utils/timezone.py +++ b/backend/utils/timezone.py @@ -5,11 +5,17 @@ from backend.core.conf import settings +# 基于 wikipedia:https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List +_UTC_IDENTIFIERS = frozenset({'Etc/UCT', 'Etc/Universal', 'Etc/UTC', 'Etc/Zulu', 'UCT', 'Universal', 'UTC', 'Zulu'}) + class TimeZone: def __init__(self) -> None: """初始化时区转换器""" - self.tz_info = zoneinfo.ZoneInfo(settings.DATETIME_TIMEZONE) + if settings.DATETIME_TIMEZONE in _UTC_IDENTIFIERS: + self.tz_info = datetime_timezone.utc + else: + self.tz_info = zoneinfo.ZoneInfo(settings.DATETIME_TIMEZONE) def now(self) -> datetime: """获取当前时区时间""" diff --git a/backend/utils/trace_id.py b/backend/utils/trace_id.py index e07114d..4208bf8 100644 --- a/backend/utils/trace_id.py +++ b/backend/utils/trace_id.py @@ -1,9 +1,3 @@ -from typing import Any - -from opentelemetry import trace -from starlette.requests import Request -from starlette_context.plugins import Plugin - from backend.common.context import ctx from backend.core.conf import settings @@ -13,19 +7,3 @@ def get_request_trace_id() -> str: if ctx.exists(): return ctx.get(settings.TRACE_ID_REQUEST_HEADER_KEY, settings.TRACE_ID_LOG_DEFAULT_VALUE) return settings.TRACE_ID_LOG_DEFAULT_VALUE - - -class OtelTraceIdPlugin(Plugin): - """OpenTelemetry Trace ID 插件""" - - key = settings.TRACE_ID_REQUEST_HEADER_KEY - - async def process_request(self, request: Request) -> Any: - """从 OpenTelemetry span 中提取 trace_id""" - span = trace.get_current_span() - span_ctx = span.get_span_context() - - if span_ctx.is_valid: - return trace.format_trace_id(span_ctx.trace_id) - - return settings.TRACE_ID_LOG_DEFAULT_VALUE diff --git a/deploy/backend/docker-compose/.env.docker b/deploy/backend/docker-compose/.env.docker index d09e805..b42bbd4 100644 --- a/deploy/backend/docker-compose/.env.docker +++ b/deploy/backend/docker-compose/.env.docker @@ -1,4 +1,10 @@ -# Docker -DOCKER_MYSQL_MAP_PORT=13306 -DOCKER_POSTGRES_MAP_PORT=15432 -DOCKER_REDIS_MAP_PORT=16379 +# Docker Port Mapping +DOCKER_MAP_SERVER_PORT=8001 +DOCKER_MAP_POSTGRES_PORT=15432 +DOCKER_MAP_MYSQL_PORT=13306 +DOCKER_MAP_REDIS_PORT=16379 +DOCKER_MAP_NGINX_PORT=8000 + +# Docker Log Mapping +# Production environment recommendation: /var/log/fba +DOCKER_FBA_SERVER_LOG=./logs/fba diff --git a/deploy/backend/grafana/dashboards/fba_server.json b/deploy/backend/grafana/dashboards/fba_server.json deleted file mode 100644 index 8bed4ea..0000000 --- a/deploy/backend/grafana/dashboards/fba_server.json +++ /dev/null @@ -1,1892 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "注释与警报", - "type": "dashboard" - } - ] - }, - "description": "FastAPI Best Architecture 应用监控仪表盘 - 实时监控 API 请求、响应时间、错误率和系统日志", - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 1, - "id": null, - "links": [ - { - "asDropdown": false, - "icon": "external link", - "includeVars": false, - "keepTime": true, - "tags": [ - "celery" - ], - "targetBlank": true, - "title": "Celery 监控", - "type": "dashboards" - } - ], - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 100, - "title": "📊 服务概览", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "过去24小时内所有 API 端点的总请求数量", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#3498DB", - "value": null - }, - { - "color": "#2980B9", - "value": 1000 - }, - { - "color": "#1F618D", - "value": 10000 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 0, - "y": 1 - }, - "id": 1, - "options": { - "colorMode": "background_solid", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(fba_request_total{app_name=\"$app_name\", path!=\"/metrics\"})", - "instant": true, - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": "24h", - "title": "总请求数", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "当前每秒处理的请求数量,反映系统实时负载情况", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#2ECC71", - "value": null - }, - { - "color": "#27AE60", - "value": 50 - }, - { - "color": "#1E8449", - "value": 100 - } - ] - }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 4, - "y": 1 - }, - "id": 2, - "options": { - "colorMode": "background_solid", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(rate(fba_request_total{app_name=\"$app_name\", path!=\"/metrics\"}[5m]))", - "instant": true, - "legendFormat": "", - "refId": "A" - } - ], - "title": "当前 QPS", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "2xx 成功响应占总响应的百分比,反映服务健康程度。低于 95% 需要关注", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [], - "max": 1, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#E74C3C", - "value": null - }, - { - "color": "#F39C12", - "value": 0.9 - }, - { - "color": "#2ECC71", - "value": 0.95 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 8, - "y": 1 - }, - "id": 3, - "options": { - "colorMode": "background_solid", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(fba_response_total{app_name=\"$app_name\", status_code=~\"2.*\", path!=\"/metrics\"}) / sum(fba_response_total{app_name=\"$app_name\", path!=\"/metrics\"})", - "instant": true, - "legendFormat": "", - "refId": "A" - } - ], - "title": "成功率", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "过去24小时内捕获的异常总数,包括所有类型的错误", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#2ECC71", - "value": null - }, - { - "color": "#F39C12", - "value": 10 - }, - { - "color": "#E74C3C", - "value": 50 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 12, - "y": 1 - }, - "id": 4, - "options": { - "colorMode": "background_solid", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(fba_exception_total{app_name=\"$app_name\"}) or vector(0)", - "instant": true, - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": "24h", - "title": "异常总数", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "所有请求的平均响应时间(毫秒),反映整体性能", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#2ECC71", - "value": null - }, - { - "color": "#F39C12", - "value": 100 - }, - { - "color": "#E74C3C", - "value": 500 - } - ] - }, - "unit": "ms" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 16, - "y": 1 - }, - "id": 5, - "options": { - "colorMode": "background_solid", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(fba_request_cost_time_sum{app_name=\"$app_name\", path!=\"/metrics\"}) / sum(fba_request_cost_time_count{app_name=\"$app_name\", path!=\"/metrics\"})", - "legendFormat": "", - "refId": "A" - } - ], - "title": "平均响应时间", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "当前正在处理中的请求数量,反映系统并发情况", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "#27AE60", - "value": null - }, - { - "color": "#D68910", - "value": 10 - }, - { - "color": "#C0392B", - "value": 50 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 20, - "y": 1 - }, - "id": 6, - "options": { - "colorMode": "background_solid", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(fba_request_in_progress{app_name=\"$app_name\", path!=\"/metrics\"}) or vector(0)", - "instant": true, - "legendFormat": "", - "refId": "A" - } - ], - "title": "进行中请求", - "type": "stat" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 101, - "title": "📈 请求趋势", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "每秒请求数的时间序列图,按 API 路径分组显示,用于观察流量模式和峰值。Mean=时间范围内平均值,Max=最大值", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "请求/秒", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 6 - }, - "id": 10, - "options": { - "legend": { - "calcs": [ - "mean", - "max" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "width": 300 - }, - "tooltip": { - "hideZeros": true, - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "rate(fba_request_total{app_name=\"$app_name\", path!=\"/metrics\"}[5m])", - "legendFormat": "{{method}} {{path}}", - "refId": "A" - } - ], - "title": "请求速率 (QPS)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "当前正在处理的请求数量趋势,用于监控系统并发负载。Mean=平均并发数,Max=峰值并发数", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "请求数", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 12, - "x": 12, - "y": 6 - }, - "id": 11, - "options": { - "legend": { - "calcs": [ - "mean", - "max" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "width": 300 - }, - "tooltip": { - "hideZeros": true, - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "fba_request_in_progress{app_name=\"$app_name\", path!=\"/metrics\"}", - "legendFormat": "{{method}} {{path}}", - "refId": "A" - } - ], - "title": "进行中请求趋势", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 16 - }, - "id": 103, - "title": "✅ 响应状态", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "2xx 成功响应占比趋势,绿色区域表示健康(>95%),红色区域表示需要关注(<80%)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "成功率", - "axisPlacement": "auto", - "axisSoftMax": 1, - "axisSoftMin": 0, - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "area" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "yellow", - "value": 0.8 - }, - { - "color": "green", - "value": 0.95 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 0, - "y": 17 - }, - "id": 30, - "options": { - "legend": { - "calcs": [ - "mean", - "min" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum by(path) (fba_response_total{app_name=\"$app_name\", status_code=~\"2.*\", path!=\"/metrics\"}) / sum by(path) (fba_response_total{app_name=\"$app_name\", path!=\"/metrics\"})", - "legendFormat": "{{path}}", - "refId": "A" - } - ], - "title": "2xx 成功率趋势", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "5xx 服务器错误占比趋势,绿色区域表示健康(<1%),红色区域表示严重问题(>10%)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "错误率", - "axisPlacement": "auto", - "axisSoftMax": 0.2, - "axisSoftMin": 0, - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "area" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 0.01 - }, - { - "color": "red", - "value": 0.1 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 8, - "y": 17 - }, - "id": 31, - "options": { - "legend": { - "calcs": [ - "mean", - "max" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum by(path) (fba_response_total{app_name=\"$app_name\", status_code=~\"5.*\", path!=\"/metrics\"}) / sum by(path) (fba_response_total{app_name=\"$app_name\", path!=\"/metrics\"})", - "legendFormat": "{{path}}", - "refId": "A" - } - ], - "title": "5xx 错误率趋势", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "各 API 端点的请求数量分布,显示哪些端点最繁忙", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 16, - "y": 17 - }, - "id": 32, - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "values": [ - "value", - "percent" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum by(path) (fba_request_total{app_name=\"$app_name\", path!=\"/metrics\"})", - "legendFormat": "{{path}}", - "refId": "A" - } - ], - "title": "请求分布", - "type": "piechart" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 25 - }, - "id": 102, - "title": "⏱️ 响应时间", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "各 API 端点的平均响应时间,颜色从绿到红表示响应时间从快到慢", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "filterable": false, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 50 - }, - { - "color": "red", - "value": 200 - } - ] - }, - "unit": "ms" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "方法" - }, - "properties": [ - { - "id": "custom.filterable", - "value": true - }, - { - "id": "custom.width", - "value": 80 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "接口" - }, - "properties": [ - { - "id": "custom.width", - "value": 800 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "平均响应时间" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "mode": "lcd", - "type": "gauge", - "valueDisplayMode": "text" - } - } - ] - } - ] - }, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 34 - }, - "id": 20, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "mean" - ], - "show": false - }, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "平均响应时间" - } - ] - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "fba_request_cost_time_sum{app_name=\"$app_name\", path!=\"/metrics\"} / fba_request_cost_time_count{app_name=\"$app_name\", path!=\"/metrics\"}", - "format": "table", - "instant": true, - "legendFormat": "__auto", - "refId": "A" - } - ], - "title": "平均响应时间", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "__name__": true, - "app_name": true, - "instance": true, - "job": true - }, - "indexByName": {}, - "renameByName": { - "Value": "平均响应时间", - "method": "方法", - "path": "接口" - } - } - } - ], - "type": "table" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "99% 的请求在此时间内完成(P99 延迟),用于评估最差情况下的用户体验。Mean=平均P99,Max=最大P99", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "延迟 (ms)", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "line+area" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 100 - }, - { - "color": "red", - "value": 500 - } - ] - }, - "unit": "ms" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 26 - }, - "id": 21, - "options": { - "legend": { - "calcs": [ - "mean", - "max" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": true, - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum(rate(fba_request_cost_time_bucket{app_name=\"$app_name\", path!=\"/metrics\"}[5m])) by(path, le))", - "legendFormat": "{{path}}", - "refId": "A" - } - ], - "title": "P99 响应时间", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "95% 的请求在此时间内完成(P95 延迟),用于评估大多数用户的体验。Mean=平均P95,Max=最大P95", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "延迟 (ms)", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "line+area" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 50 - }, - { - "color": "red", - "value": 200 - } - ] - }, - "unit": "ms" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 12, - "x": 12, - "y": 26 - }, - "id": 22, - "options": { - "legend": { - "calcs": [ - "mean", - "max" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": true, - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum(rate(fba_request_cost_time_bucket{app_name=\"$app_name\", path!=\"/metrics\"}[5m])) by(path, le))", - "legendFormat": "{{path}}", - "refId": "A" - } - ], - "title": "P95 响应时间", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 34 - }, - "id": 104, - "title": "🔍 链路追踪", - "type": "row" - }, - { - "datasource": { - "type": "tempo", - "uid": "tempo" - }, - "description": "FastAPI 服务的分布式追踪数据,点击 Trace ID 可跳转到 Explore 查看完整调用链,支持通过顶部「接口筛选」变量过滤", - "fieldConfig": { - "defaults": { - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - }, - "filterable": false, - "inspect": false - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Service" - }, - "properties": [ - { - "id": "custom.hidden", - "value": true - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Trace ID" - }, - "properties": [ - { - "id": "custom.width", - "value": 350 - }, - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "color", - "value": { - "fixedColor": "#6E9FFF", - "mode": "fixed" - } - }, - { - "id": "links", - "value": [ - { - "title": "查看调用链", - "url": "/explore?orgId=1&left={\"datasource\":\"tempo\",\"queries\":[{\"refId\":\"A\",\"queryType\":\"traceql\",\"query\":\"${__value.raw}\"}],\"range\":{\"from\":\"now-1h\",\"to\":\"now\"}}" - } - ] - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Start time" - }, - "properties": [ - { - "id": "custom.width", - "value": 200 - }, - { - "id": "displayName", - "value": "时间" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Name" - }, - "properties": [ - { - "id": "displayName", - "value": "接口" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Duration" - }, - "properties": [ - { - "id": "custom.width", - "value": 120 - }, - { - "id": "displayName", - "value": "耗时" - }, - { - "id": "custom.cellOptions", - "value": { - "type": "gauge", - "mode": "gradient", - "valueDisplayMode": "text" - } - }, - { - "id": "color", - "value": { - "mode": "continuous-GrYlRd" - } - }, - { - "id": "max", - "value": 500 - } - ] - } - ] - }, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 35 - }, - "id": 40, - "options": { - "showHeader": true, - "cellHeight": "sm", - "footer": { - "show": false - } - }, - "transformations": [ - { - "id": "convertFieldType", - "options": { - "conversions": [ - { - "targetField": "Start time", - "destinationType": "time" - } - ] - } - }, - { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [ - { - "field": "Start time", - "desc": true - } - ] - } - } - ], - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "tempo", - "uid": "tempo" - }, - "queryType": "traceql", - "query": "{ span.http.method != \"OPTIONS\" && span.http.target !~ \"/metrics.*\" && span.http.target !~ \"/ws.*\" && trace:id =~ \".*${trace_id}.*\" }", - "limit": 20, - "refId": "A", - "tableType": "traces" - } - ], - "title": "服务 Traces", - "type": "table" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 45 - }, - "id": 105, - "title": "📝 应用日志", - "type": "row" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "按级别统计日志产生速率,ERROR/CRITICAL 级别增加需立即关注", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "日志/秒", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "bars", - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "DEBUG" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "#85C1E9", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "INFO" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "#52BE80", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "WARNING" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "#F39C12", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "ERROR" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "#E74C3C", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "CRITICAL" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "#8E44AD", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 12, - "w": 8, - "x": 0, - "y": 46 - }, - "id": 50, - "options": { - "legend": { - "calcs": [ - "sum" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": true, - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "editorMode": "code", - "expr": "(rate({service_name=\"$app_name\"} |= \"$trace_id\" [1m]))", - "legendFormat": "{{level}}", - "refId": "A" - } - ], - "title": "日志级别统计", - "type": "timeseries" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "实时应用日志流,支持通过顶部「日志搜索」变量过滤", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 12, - "w": 16, - "x": 8, - "y": 46 - }, - "id": 51, - "options": { - "dedupStrategy": "none", - "enableInfiniteScrolling": true, - "enableLogDetails": true, - "prettifyLogMessage": false, - "showCommonLabels": false, - "showLabels": false, - "showTime": false, - "sortOrder": "Descending", - "wrapLogMessage": false - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "editorMode": "code", - "expr": "{service_name=\"$app_name\"} | json | line_format \"{{.body}}\" |= \"$trace_id\"", - "refId": "A" - } - ], - "title": "实时日志", - "type": "logs" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "仅显示 ERROR 和 CRITICAL 级别日志,支持通过「日志搜索」变量过滤", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 58 - }, - "id": 52, - "options": { - "dedupStrategy": "none", - "enableInfiniteScrolling": true, - "enableLogDetails": true, - "prettifyLogMessage": false, - "showCommonLabels": false, - "showLabels": false, - "showTime": false, - "sortOrder": "Descending", - "wrapLogMessage": false - }, - "pluginVersion": "12.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "editorMode": "code", - "expr": "{service_name=\"$app_name\"} | json | level=~\"ERROR|CRITICAL\" | line_format \"{{.body}}\" |= \"$trace_id\"", - "refId": "A" - } - ], - "title": "错误日志", - "type": "logs" - } - ], - "preload": false, - "refresh": "5s", - "schemaVersion": 41, - "tags": [ - "fastapi", - "fba_server" - ], - "templating": { - "list": [ - { - "current": { - "selected": true, - "text": "fba 服务", - "value": "fba_server" - }, - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "definition": "label_values(fba_app_info, app_name)", - "description": "数据来自哪个应用", - "label": "应用名称", - "name": "app_name", - "query": { - "qryType": 1, - "query": "label_values(fba_app_info, app_name)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "type": "query" - }, - { - "current": { - "text": "", - "value": "" - }, - "description": "输入 Trace ID 同时搜索 Traces 和日志", - "label": "Trace ID", - "name": "trace_id", - "options": [ - { - "selected": true, - "text": "", - "value": "" - } - ], - "query": "", - "type": "textbox" - } - ] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "title": "FastAPI Best Architecture", - "uid": "fba_server", - "version": 1 -} diff --git a/deploy/backend/grafana/fba_config.alloy b/deploy/backend/grafana/fba_config.alloy deleted file mode 100644 index bba9ef2..0000000 --- a/deploy/backend/grafana/fba_config.alloy +++ /dev/null @@ -1,158 +0,0 @@ -// ============================================================================ -// Grafana Alloy 配置文件 -// Alloy 是 Grafana 的可观测性数据收集器(原 Grafana Agent) -// 官方文档: https://grafana.com/docs/alloy/latest/ -// -// 数据流向: -// 应用程序 -> OTLP Receiver -> Batch Processor -> Exporters -> 后端存储 -// - 日志 (Logs) -> Loki -// - 指标 (Metrics) -> Prometheus -// - 链路 (Traces) -> Tempo -// ============================================================================ - -// OTLP 接收器配置 -// OpenTelemetry Protocol (OTLP) 是 OpenTelemetry 的标准数据传输协议 -// 支持同时接收日志、指标和链路追踪数据 -otelcol.receiver.otlp "default" { - // gRPC 协议配置 - // gRPC 是高性能的 RPC 框架,适用于服务间通信 - grpc { - endpoint = "0.0.0.0:4317" - // 0.0.0.0 表示监听所有网络接口 - // 4317 是 OTLP gRPC 的标准端口 - // 应用程序通过此端口发送遥测数据 - } - - // HTTP 协议配置 - // HTTP 协议更通用,适用于无法使用 gRPC 的场景 - http { - endpoint = "0.0.0.0:4318" - // 4318 是 OTLP HTTP 的标准端口 - // 支持 JSON 和 Protobuf 格式的数据 - } - - // 输出配置 - // 定义接收到的数据发送到哪些处理器 - output { - logs = [otelcol.processor.batch.default.input] - metrics = [otelcol.processor.batch.default.input] - traces = [otelcol.processor.batch.default.input] - } -} - -// 批处理器配置 -// 将多个遥测数据项合并成批次,提高传输效率 -otelcol.processor.batch "default" { - // 输出配置 - // 定义处理后的数据发送到哪些导出器 - output { - logs = [otelcol.exporter.loki.default.input] - metrics = [otelcol.exporter.prometheus.default.input] - traces = [otelcol.exporter.otlp.tempo.input] - } -} - -// Loki 导出器配置 -// 将日志数据发送到 Grafana Loki -otelcol.exporter.loki "default" { - forward_to = [loki.write.loki_server.receiver] -} - -// Loki 写入组件 -// 负责将日志数据推送到 Loki 服务器 -loki.write "loki_server" { - endpoint { - url = "http://fba_loki:3100/loki/api/v1/push" - } -} - -// Prometheus 导出器配置 -// 将指标数据发送到 Prometheus -otelcol.exporter.prometheus "default" { - forward_to = [prometheus.remote_write.prom_server.receiver] -} - -// Prometheus 远程写入组件 -// 负责将指标数据推送到 Prometheus 服务器 -prometheus.remote_write "prom_server" { - endpoint { - url = "http://fba_prometheus:9090/api/v1/write" - } -} - -// Prometheus 自身指标抓取 -// 监控 Prometheus 服务器本身的运行状态 -prometheus.scrape "prometheus" { - targets = [{ - __address__ = "fba_prometheus:9090", - }] - forward_to = [prometheus.remote_write.prom_server.receiver] - // 抓取的指标发送到远程写入组件 - - job_name = "fba_prometheus" - // 任务名称 - // 将作为 job 标签添加到所有抓取的指标上 - - scrape_interval = "15s" - // 抓取间隔 -} - -// FastAPI 后端服务指标抓取 -// 监控 FastAPI 应用的性能和业务指标 -prometheus.scrape "fba_server" { - targets = [{ - __address__ = "fba_server:8001", - }] - forward_to = [prometheus.remote_write.prom_server.receiver] - // 抓取的指标发送到远程写入组件 - - job_name = "fba_server" - // 任务名称 - // 用于在 Prometheus 查询中区分不同服务 - - scrape_interval = "5s" - // 抓取间隔 - - scrape_timeout = "5s" - // 抓取超时时间 -} - -// Celery Exporter 指标抓取 -// 监控 Celery 异步任务队列的运行状态 -prometheus.scrape "fba_celery_exporter" { - targets = [{ - __address__ = "fba_celery_exporter:9808", - }] - forward_to = [prometheus.remote_write.prom_server.receiver] - // 抓取的指标发送到远程写入组件 - - job_name = "fba_celery_exporter" - // 任务名称 - // 用于标识 Celery 相关的指标 - - scrape_interval = "5s" - // 抓取间隔 - - scrape_timeout = "5s" - // 抓取超时时间 -} - -// Tempo 导出器配置 -// 将链路追踪数据发送到 Grafana Tempo -otelcol.exporter.otlp "tempo" { - client { - endpoint = "fba_tempo:4317" - - tls { - insecure = true - // 禁用 TLS 加密 - // 在 Docker 内部网络中通信时可以禁用 - // 生产环境建议启用 TLS - - insecure_skip_verify = true - // 跳过 TLS 证书验证 - // 仅在开发/测试环境使用 - // 生产环境应配置正确的证书 - } - } -} diff --git a/deploy/backend/grafana/fba_dashboards.yml b/deploy/backend/grafana/fba_dashboards.yml deleted file mode 100644 index caceab3..0000000 --- a/deploy/backend/grafana/fba_dashboards.yml +++ /dev/null @@ -1,36 +0,0 @@ -# ============================================================================ -# Grafana 仪表盘 Provisioning 配置文件 -# 官方文档: https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards -# 用于自动加载和管理仪表盘,无需手动导入 -# ============================================================================ - -# API 版本号 -# 当前仅支持版本 1 -apiVersion: 1 - -# providers: 仪表盘提供者列表 -# 定义 Grafana 从哪里加载仪表盘以及如何管理它们 -providers: - - name: 'fba_server' - # 提供者名称,用于标识和日志记录 - - orgId: 1 - # 组织 ID - # 指定仪表盘属于哪个组织 - # 默认值: 1(默认组织) - - type: 'file' - # 提供者类型 - # 可选值: - # - file: 从本地文件系统加载仪表盘 JSON 文件 - - disableDeletion: true - # 禁止通过 UI 删除仪表盘 - - editable: false - # 是否允许通过 UI 编辑仪表盘 - - options: - path: '/etc/grafana/dashboards' - # 仪表盘 JSON 文件所在目录(容器内路径) - # Grafana 会自动扫描此目录下的所有 .json 文件 diff --git a/deploy/backend/grafana/fba_datasource.yml b/deploy/backend/grafana/fba_datasource.yml deleted file mode 100644 index 8bab48c..0000000 --- a/deploy/backend/grafana/fba_datasource.yml +++ /dev/null @@ -1,164 +0,0 @@ -# ============================================================================ -# Grafana 数据源 Provisioning 配置文件 -# 官方文档: https://grafana.com/docs/grafana/latest/administration/provisioning/#datasources -# 用于自动配置数据源,实现基础设施即代码 -# ============================================================================ - -# API 版本号 -# 当前仅支持版本 1 -apiVersion: 1 - -# datasources: 数据源列表 -# 定义 Grafana 连接的所有后端数据存储 -datasources: - # ========================================================================== - # Loki 数据源配置 - # Loki 是 Grafana 的日志聚合系统,类似于 Prometheus 但用于日志 - # 官方文档: https://grafana.com/docs/loki/latest/ - # ========================================================================== - - name: Loki - # 数据源显示名称,在 Grafana UI 中显示 - - uid: loki - # 数据源唯一标识符 - # 用于在仪表盘和告警规则中引用此数据源 - - type: loki - # 数据源类型 - # 必须与 Grafana 支持的数据源插件类型匹配 - - url: http://fba_loki:3100 - # Loki 服务器地址 - - isDefault: false - # 是否设为默认数据源 - - jsonData: - # 数据源特定的 JSON 配置 - - derivedFields: - # 派生字段配置 - # 用于从日志中提取字段并创建链接到其他数据源 - # 这是实现日志到链路追踪关联的关键配置 - - - datasourceUid: tempo - # 目标数据源的 UID - # 点击链接时将跳转到此数据源 - - matcherRegex: '"traceid":"([a-f0-9]{32})"' - # 正则表达式,用于从日志内容中提取 Trace ID - - matcherType: regex - # 匹配器类型 - # 可选值: regex(正则表达式) - - name: TraceID - # 派生字段的名称 - # 将显示在日志详情中 - - url: $${__value.raw} - # 链接 URL 模板 - # $${__value.raw} 表示提取的原始值 - - urlDisplayLabel: 查看 Trace - # 链接显示的文本标签 - - # ========================================================================== - # Prometheus 数据源配置 - # Prometheus 是云原生监控系统,用于收集和存储时序指标数据 - # 官方文档: https://prometheus.io/docs/ - # ========================================================================== - - name: Prometheus - # 数据源显示名称 - - uid: prometheus - # 数据源唯一标识符 - - type: prometheus - # 数据源类型 - - url: http://fba_prometheus:9090 - # Prometheus 服务器地址 - - isDefault: true - # 设为默认数据源 - - jsonData: - httpMethod: POST - # 查询使用的 HTTP 方法 - - exemplarTraceIdDestinations: - # Exemplar(范例)配置 - # Exemplar 是指标数据点关联的 Trace ID - # 用于从指标跳转到对应的链路追踪 - - - name: TraceID - # Exemplar 中 Trace ID 的字段名称 - - datasourceUid: tempo - # 目标 Tempo 数据源的 UID - # 点击 Exemplar 时将跳转到此数据源查看 Trace - - # ========================================================================== - # Tempo 数据源配置 - # Tempo 是 Grafana 的分布式链路追踪后端 - # 官方文档: https://grafana.com/docs/tempo/latest/ - # ========================================================================== - - name: Tempo - # 数据源显示名称 - - uid: tempo - # 数据源唯一标识符 - - type: tempo - # 数据源类型 - - url: http://fba_tempo:3200 - # Tempo 服务器地址 - - jsonData: - nodeGraph: - enabled: true - # 启用节点图功能 - # 可视化展示服务之间的调用关系图 - - tracesToLogsV2: - # Trace 到日志的关联配置(V2 版本) - # 允许从 Trace 详情页面跳转到相关日志 - - datasourceUid: loki - # 日志数据源的 UID - - filterByTraceID: true - # 是否按 Trace ID 过滤日志 - - spanStartTimeShift: "-1m" - # Span 开始时间偏移 - # 用于捕获 Span 开始前的相关日志 - - spanEndTimeShift: "1m" - # Span 结束时间偏移 - # 用于捕获 Span 结束后的相关日志 - - customQuery: true - # 启用自定义查询 - # 允许使用下面的 query 字段自定义日志查询语句 - - query: '{service_name="fba_server"} | json | traceid="${__span.traceId}"' - # 自定义 LogQL 查询语句 - - search: - hide: false - # 是否隐藏搜索功能 - - serviceMap: - datasourceUid: prometheus - # 服务地图数据源 - # 使用 Prometheus 中的指标数据生成服务依赖图 - # 需要 Tempo 的 metrics_generator 功能配合 - - lokiSearch: - datasourceUid: loki - # Loki 搜索数据源 - # 允许在 Tempo 中使用 Loki 进行日志搜索 - # 实现基于日志内容查找 Trace 的功能 diff --git a/deploy/backend/grafana/fba_grafana.ini b/deploy/backend/grafana/fba_grafana.ini deleted file mode 100644 index 4c444d9..0000000 --- a/deploy/backend/grafana/fba_grafana.ini +++ /dev/null @@ -1,49 +0,0 @@ -# ============================================================================ -# Grafana 主配置文件 -# 官方文档: https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/ -# ============================================================================ - -# [server] 服务器配置 -# 用于配置 Grafana HTTP 服务器的基本设置 -[server] -# 协议类型 -protocol = http - -# HTTP 服务监听端口 -http_port = 3000 - -# [security] 安全配置 -# 用于配置 Grafana 安全相关的默认设置 -[security] -# 默认 Grafana 管理员用户名 -admin_user = admin - -# 默认 Grafana 管理员密码 -admin_password = 123456 - -# 如果您将 Grafana 托管在 HTTPS -;cookie_secure = true - -# 指示浏览器允许在 ,