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
+
+
+
+
+[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
+
+
+
+
+
+
+[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
+
+
+
+
+[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
+
+
+
+[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
+
+
+
+[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
-
-# 指示浏览器允许在 ,