Compare commits

...
439 Commits
Author SHA1 Message Date
Wu Clan 27383e4ff5 Fix ctx in validation exception handler (#861)
* Fix ctx in validation exception handler

* Fix lint
2025-10-17 16:21:28 +08:00
Wu Clan bf16f8916d Update the changelog for v1.9.0 (#856) 2025-10-17 13:05:31 +08:00
Wu Clan 75a62ac6aa Fix dict plugin pgsql init script missing (#857)
* Fix dict plugin pgsql init script missing

* Fix lint
2025-10-17 13:05:10 +08:00
Wu Clan bbc4aaa020 Fix the api ninja in code generation (#858) 2025-10-17 13:04:54 +08:00
Wu Clan 89e5d02fe2 Update unique judgment logic for dict data (#859) 2025-10-17 13:04:35 +08:00
Wu Clan a93973af06 Fix the import table cli command (#860) 2025-10-17 13:02:28 +08:00
Wu Clan 15a9527866 Refactor the service layer db session call method (#850)
* Refactor the service layer db session call method

* Fix lint

* Update paging interfaces

* Fix some errors
2025-10-16 12:49:56 +08:00
Wu Clan 9147d35c8c Fix safely of access ctx in exception handlers (#855) 2025-10-16 12:45:01 +08:00
Wu Clan 6a065797f5 Update request state usage to context variable (#853)
* Update request state usage to context variable

* Update context to custom ctx

* Fix the exception interception in opera log

* restore elapsed
2025-10-16 10:23:39 +08:00
Wu Clan dd08775c85 Fix the superuser verify missing auth (#854) 2025-10-14 17:55:54 +08:00
Wu Clan 0cbe635ed8 Update superuser verify to dependency injection (#852)
* Update superuser verify to dependency injection

* Update sql scripts
2025-10-14 17:38:47 +08:00
Wu Clan e923f13e83 Bump fastapi and pydantic to the latest (#851) 2025-10-14 15:18:35 +08:00
Wu Clan 889da744ef Update the changelog for v1.8.3 (#849) 2025-10-13 17:31:23 +08:00
Wu Clan 74df1d1ce2 Bump sqlalchemy crud plus from 0.12.0 to 0.12.1 (#848) 2025-10-13 10:45:49 +08:00
Wu Clan 54e09b68c7 Bump fastapi and sqlalchemy to support python 3.14 (#847) 2025-10-11 13:19:10 +08:00
Wu Clan 5762834744 Update the ruff rules and format the code (#846)
* Update the ruff rules and format the code

* Update the per-file-ignores

* Update the ci

* Update rules

* Fix codes

* Fix pagination

* Update rules
2025-10-10 19:02:49 +08:00
Wu Clan 51354593d0 Bump dependencies and pre-commits (#845) 2025-10-09 13:26:56 +08:00
Wu Clan 8751425e2b Fix SQL script syntax error in config plugin (#843) 2025-09-27 12:33:58 +08:00
Wu Clan 921f05fa19 Fix the pgsql script in dict plugin (#842) 2025-09-27 12:17:45 +08:00
Wu Clan 5754c5449d Update the changelog for v1.8.2 (#841) 2025-09-26 13:32:05 +08:00
Wu Clan 2f535e3510 Update the version number to 1.8.2 (#840) 2025-09-26 13:28:41 +08:00
Wu Clan c25e447530 Update docker and deployment script comments (#839)
* Update docker and deployment script comments

* Update celery stdout logfile conf
2025-09-26 13:18:34 +08:00
Wu Clan 13359b736c Fix the phone filter in the user list (#838) 2025-09-26 12:08:53 +08:00
Wu Clan 500f20d91c Add extension parameters to paging data (#835) 2025-09-26 12:03:41 +08:00
Wu Clan d4de42fb46 Add the refresh token to demo mode exclusion (#834) 2025-09-26 12:03:27 +08:00
Wu Clan 3f0537a727 Simplify the code generation CLI parameters (#837) 2025-09-26 12:03:08 +08:00
Wu Clan d1cdb9e462 Fix the get model object method return logic (#832)
* Fix the get model object method return

* Update the error trigger method

* Delete the error log
2025-09-22 20:51:32 +08:00
Wu Clan 6c029b9be7 Fix code generation template missing Decimal (#830)
* Fix code generation template missing Decimal

* Update template rendering

* Update version number

* Fix lint
2025-09-22 10:46:13 +08:00
Wu Clan df12a0c701 Update the user agent in opera log model (#831) 2025-09-22 10:42:26 +08:00
Wu Clan 0be5685d9f Add version number output to the startup CLI (#820) 2025-09-17 11:11:50 +08:00
Wu Clan 3efca8af37 Update the dict plugin table structure (#817)
* Update the dict plugin table structure

* Update dict sql

* FIx the dict plugin sql
2025-09-15 09:04:30 +08:00
Wu Clan 82b2f808ac Fix the summary in opera log middleware (#816) 2025-09-14 13:22:24 +08:00
Wu Clan f417374f3e Update the login captcha verify logic (#815) 2025-09-12 21:02:21 +08:00
Wu Clan 237534c63e Update the startup progress information display (#812) 2025-09-12 15:44:17 +08:00
morty zhao b525124fdf Fix special character password support in alembic (#811) 2025-09-12 11:07:55 +08:00
Wu Clan 5217c95451 Fix summary default in opera log middleware (#809) 2025-09-11 18:21:40 +08:00
B ea973cac90 Fix pgsql syntax error in code generation (#808)
* Update title field in OperaLogSchemaBase to allow None values

* Update response type in get_all_tables to allow None values in dictionary

* Fix code generation error in PostgreSQL

* Update opera_log.py
2025-09-11 17:53:15 +08:00
Wu Clan 1bedda0e66 Update the offline location information parse (#807) 2025-09-11 14:49:09 +08:00
B c1087fa51d Fix the subprocess ensurepip got stuck in Linux (#806)
* Fix ensurepip_install

* fix code format
2025-09-10 19:42:13 +08:00
Wu Clan cfddf44e7e Update the swagger docs version to dynamic (#805) 2025-09-09 19:36:33 +08:00
Wu Clan 085b32e921 Update the changelog for v1.8.1 (#804) 2025-09-09 19:19:30 +08:00
Wu Clan 2099c33aad Fix alembic migration file missing import (#803) 2025-09-09 17:21:23 +08:00
yzbf b809497aba Fix the subprocess check call failed in windows (#802)
* fix: 修复Windows下subprocess调用失败的问题

* fix: 修复代码格式问题
2025-09-09 16:21:51 +08:00
Wu Clan a6fe8c0e8d Fix console import in main module (#800) 2025-09-07 19:33:55 +08:00
Wu Clan 629cf7ce45 Optimize the code generation interaction logic (#799) 2025-09-06 19:24:02 +08:00
Wu Clan fad6c05d41 Add the code generator to the CLI (#798)
* Add the code generator to the CLI

* Optimize run_await implementation

* Optimize interactions
2025-09-05 23:43:05 +08:00
Wu Clan 77d59d3453 Add schema for the tree data (#796) 2025-09-04 16:38:53 +08:00
Wu Clan 931d6b281a Fix the psycopg dependency naming (#795) 2025-09-04 11:54:39 +08:00
Wu Clan d929f0dbdc Fix sql script syntax error in config plugin (#793) 2025-09-03 19:57:11 +08:00
Wu Clan 6d1b47903e Add the dict type query all interface (#794) 2025-09-03 19:56:56 +08:00
Wu Clan 8420ccf190 Update the notice model and pagination (#792) 2025-09-02 15:51:07 +08:00
Wu Clan 1e38e1918b Bump dependencies and pre-commits (#791) 2025-09-02 12:45:34 +08:00
IAseven 52e78152a1 Fix the tzinfo comparison in the timezone type (#787) 2025-08-29 10:30:42 +08:00
Wu Clan dcb07c60f2 Add the Google OAuth2 login (#790) 2025-08-28 22:10:22 +08:00
Wu Clan 33c9f78cf0 Update the handling of CORS 500 status code (#789) 2025-08-28 15:36:06 +08:00
Wu Clan 37b1033697 Update the opera log exception message record (#788) 2025-08-28 15:01:01 +08:00
Wu Clan 0aa4ebc635 Update the model datetime column type to custom (#786)
* Update the model datetime column type to custom

* Update the schema datetime filed json encoder
2025-08-27 20:07:35 +08:00
language 30d4107670 Fix CLI openapi url in production environment (#785) 2025-08-26 09:39:30 +08:00
Wu Clan dcf235db78 Fix code generation template params and filename (#784) 2025-08-22 13:20:30 +08:00
Wu Clan 1dfcd7ae3c Update the celery task result table creation logic (#783)
* Update the celery task result table creation logic

* Disable beat_sync_every config

* Update the prepared comment
2025-08-21 23:46:52 +08:00
wuyao4 2b28244099 Fix the plugin model object detection logic (#782) 2025-08-21 18:17:50 +08:00
Wu Clan f71071f905 Fix the celery beat distributed lock timeout (#779)
* Fix the celery beat distributed lock timeout

* Fix redis client not close after plugin parse

* Optimize code layout and lock extend
2025-08-21 13:50:57 +08:00
Wu Clan 34c9c398ca Fix redis client not close after plugin parse (#780) 2025-08-21 10:58:53 +08:00
Wu Clan f099777dd3 Fix the docker compose env configuration file (#778)
* Fix the docker compose env configuration file

* Fix CELERY_RABBITMQ_HOST value
2025-08-19 13:21:11 +08:00
Wu Clan c95be82cb9 Update the env parameter value in env file (#777) 2025-08-19 12:50:08 +08:00
Wu Clan 00ec039095 Add test data SQL script for config plugin (#776) 2025-08-19 12:38:22 +08:00
Wu Clan 4d087355b4 Optimize email sending config update logic (#775) 2025-08-19 12:19:31 +08:00
Wu Clan d57fabcbb4 Add bulk update interface for config plugin (#774)
* Add bulk update interface for config plugin

* Remove email sending update
2025-08-19 12:18:38 +08:00
yanlingsishao 3a32522e2b Optimize the celery crontab expression validation (#773) 2025-08-18 22:19:59 +08:00
Wu Clanandwu-clan 22f8850bf7 Update the changelog for v1.8.0 (#772)
This commit was created by changelog-from-release in 'Release changelog' CI workflow

Co-authored-by: wu-clan <wu-clan@users.noreply.github.com>
2025-08-15 20:09:28 +08:00
Wu Clan eeb709c6aa Update the version number to 1.8.0 (#771) 2025-08-15 20:06:21 +08:00
Wu Clan a6bbf2971d Update the menu title in SQL scripts (#770) 2025-08-15 19:58:46 +08:00
Dylan cd48bb4210 Add i18n support for response message (#753)
* feat: i18n support

* Optimize i18n

* Update the locale in the code

* Update the zh-CN file

* Update the en-US file

* Update the reload filter

* Update locale success plugin value

* Fix lint

* Update pydantic error message translation

* Update to minimal implementation

* Fix minimal missing code
2025-08-15 19:57:10 +08:00
Wu Clan 4500dd0128 Add a standalone email sending plugin (#769) 2025-08-13 18:09:42 +08:00
Wu Clan 2b6d8222ad Update the content layout of the config file (#768) 2025-08-13 18:07:02 +08:00
Wu Clan 1b47ab7e83 Optimize the timezone datetime return encoder (#767)
* Optimize the timezone datetime return encoder

* Update the default datetime conversion
2025-08-13 11:16:58 +08:00
Wu Clan bd804e0a38 Update the description for the run file (#766) 2025-08-12 23:28:43 +08:00
Wu Clan 8e8af2032a Optimize naming and preview in code generation (#764) 2025-08-12 16:42:25 +08:00
IAseven e09062eb39 Optimize the opera log storage logic through queue (#750)
*  feat: 操作日志中间件添加批量插入功能

* Delete GEMINI.md

* 🌈 style: 修复格式化错误

* 🐞 fix: 通过asyncio.wait_for兼容py3.10中asyncio.timeout不存在

* 🦄 refactor: 重新组织操作日志批量插入代码逻辑

* 优化代码实现

* 恢复默认配置

* 恢复默认 .gitignore 文件

* 更新队列批处理逻辑
2025-08-07 17:36:10 +08:00
Wu Clan 8c00492e44 Update the naming of table creation function (#760) 2025-08-07 16:57:57 +08:00
Wu Clan 0237d4c7b1 Update log output config and format (#759) 2025-08-07 16:46:56 +08:00
Wu Clan fe3a3b4e86 Optimize the data sort logic of tree nodes (#758) 2025-08-06 18:23:09 +08:00
Wu Clan 65ec721a1c Add business pagination in the code generator (#757) 2025-08-05 21:04:27 +08:00
Wu Clan 4eb76ad6ea Update the opera log desensitization method (#756)
* Update the opera log desensitization method

* Update request args function return
2025-08-05 18:38:49 +08:00
Wu Clan 1f8687155a Fix message format in validation exception handler (#755) 2025-08-05 18:11:02 +08:00
Wu Clan 8591d4e592 Refactor task routes and add control routes (#749) 2025-08-04 13:18:07 +08:00
Wu Clan dedf4e7bae Refactor code generation files and routes (#748)
* Refactor code generation files and routes

* Fix lint
2025-08-04 13:16:41 +08:00
Wu Clan 1e4aa88487 Fix the kwargs params of schedule task (#747) 2025-08-04 13:15:48 +08:00
Wu Clan 0dd745b7a7 Add schedule task demo that contains params (#746) 2025-07-31 18:29:54 +08:00
Wu Clan 6b2402f212 Add some interfaces for user profiles (#745) 2025-07-31 16:46:02 +08:00
Wu Clan 24a487eeea Simplify the plugin status update logic (#744) 2025-07-30 11:31:37 +08:00
Wu Clan 4f574189c7 Fix the error trigger when model auto import (#743) 2025-07-29 22:52:34 +08:00
Wu Clan b559a74cea Add update support for user email and phone (#742) 2025-07-29 22:52:08 +08:00
Wu Clan 83dcdbe59d Update the OAuth2 login password policy (#741)
* Update the OAuth2 login password policy

* Update the crud pwd

* Update the reset pwd service
2025-07-29 22:51:31 +08:00
Wu Clan d64f7c2911 Fix the opera log field encryption (#739) 2025-07-25 19:23:36 +08:00
Wu Clan 53e64bce37 Add auth whitelist regular expression config (#738) 2025-07-24 21:34:24 +08:00
Wu Clan 00a781357b Fix celery CLI option to required (#737) 2025-07-24 21:32:57 +08:00
Wu Clan d7f87ed0ad Update the default cache period for userinfo (#734) 2025-07-21 21:31:55 +08:00
Wu Clan bda9b1d463 Add distributed lock for scheduled task (#732)
* Add distributed lock for scheduled task

* Add the task to extend lock

* Fix the close
2025-07-21 12:29:12 +08:00
Wu Clan e0a106ec51 Simplify task crontab expression validation (#733) 2025-07-18 21:11:54 +08:00
Wu Clan 016361bd68 Update the changelog for v1.7.0 (#729) 2025-07-16 13:40:55 +08:00
Wu Clan f2d3c39425 Fix login and operation log clearing (#728) 2025-07-16 13:34:37 +08:00
Wu Clan e45d2d6fe7 Add CLI support for startup celery services (#724)
* Add CLI support for startup celery services

* update the cli file
2025-07-16 12:28:57 +08:00
Wu Clan 4ddf84fa22 Bump granian from 2.4.0 to 2.4.2 (#727) 2025-07-16 12:28:02 +08:00
Wu Clan 326a1883e8 Fix the parsing of execution task params (#725)
* Fix the parsing of execution task params

* Enhanced checksums
2025-07-16 12:10:33 +08:00
Wu Clan 9ff36d4498 Delete the default value of schema enum data (#723) 2025-07-15 10:43:58 +08:00
Wu Clan 98ef07ad32 Simplify celery task crontab config (#722) 2025-07-15 00:28:10 +08:00
Wu Clan 802b0d456a Optimize celery integrations and events (#721) 2025-07-14 19:17:24 +08:00
Wu Clan 6767f0e2e6 Update the celery task comment and name (#720) 2025-07-11 21:08:56 +08:00
Wu Clan d72a05c965 Fix the celery task scheduler query (#719) 2025-07-11 21:08:44 +08:00
Wu Clan ce3be1db8e Add support for celery dynamic tasks (#715)
* Add support for celery dynamic tasks

* Update the celery conf

* Update the celery task tables name

* Refactor the celery task-related interfaces

* Optimize auto-discovery tasks

* Remove redundant config

* Refine the business codes

* Optimize crontab validation returns

* Update dependencies in pyproject toml

* Fix some bugs

* Update dependencies

* Update the version to 1.7.0

* Fix update and delete event
2025-07-11 07:54:33 +08:00
Wu Clan e84ef04f15 Update the CLI startup service mode (#718) 2025-07-10 20:33:50 +08:00
Wu Clan adee3a2177 Simplify user permission database queries (#717) 2025-07-09 20:44:32 +08:00
Wu Clan 526e0aab9a Optimize the analysis of get plugins (#716)
* Optimize the analysis of get plugins

* update var
2025-07-08 12:20:13 +08:00
Wu Clan 2bbbbe764a Update the log output default style (#714)
* Update the log output default style

* Update the log file compression

* fix line

* Add log summary
2025-07-06 14:32:36 +08:00
Wu Clan ef5e921c08 Update the middleware logging accuracy (#713)
* Update the middleware logging accuracy

* Update the log style
2025-07-05 19:52:37 +08:00
Wu Clan a2fa59285a Update the granian env to command params (#712) 2025-07-04 19:46:40 +08:00
Wu Clan 494942e87e Add CLI support for execute sql scripts (#711)
* Add CLI support for execute sql scripts

* Update the arg helps
2025-07-02 20:19:29 +08:00
Wu Clan aa2b76673f Update the reload excludes for CLI run (#709) 2025-07-02 18:16:43 +08:00
Wu Clan 099880dd1c Update the refresh token verify mechanism (#710)
* Remove the logout interface auth dependency

* Update the refresh token check
2025-07-02 18:08:28 +08:00
Wu Clan d906a103af Fix the code generation delete schema template (#708) 2025-07-01 22:28:37 +08:00
Wu Clan 4ed49d2d13 Replace gunicorn deployment to granian (#705)
* Replace gunicorn deployment to granian

* Update env comments

* Fix the granian env
2025-07-01 21:33:49 +08:00
Wu Clan 7b5ae4696f Fix the code generation schema template (#706) 2025-07-01 18:18:47 +08:00
Wu Clan 54ea301152 Update the CLI to be executed async (#704) 2025-07-01 18:18:29 +08:00
Wu Clan 69c27232ac Update the changelog for v1.6.0 (#703) 2025-06-30 17:02:55 +08:00
Wu Clan f36dcb3f5c Update the version number to 1.6.0 (#702) 2025-06-30 17:00:19 +08:00
Wu Clan 63d088d62c Update the Dockerfile to adapt the CLI (#701) 2025-06-30 16:53:16 +08:00
Wu Clan a461f78224 Optimize the installation of plugin dependencies (#700)
* Optimize the installation of plugin dependencies

* Remove enumerate

* Update main params
2025-06-30 09:29:35 +08:00
Wu Clan c306432708 Update the help for CLI run worker (#699) 2025-06-29 20:06:45 +08:00
Wu Clan 97f778cc90 Add CLI support for plugin install (#698)
* Add CLI support for plugin install

* Fix some usage errors

* Update prompt information
2025-06-29 17:41:12 +08:00
Wu Clan 88695ac6ad Add custom CLI for service startup (#697)
* Add custom CLI for service startup

* Remove redundant scripts

* Fix lint
2025-06-28 14:56:19 +08:00
Wu Clan 3b04329b04 Add the test user to SQL scripts (#696) 2025-06-27 18:43:42 +08:00
Wu Clan bd4acf8121 Update the extension plugin config (#695) 2025-06-27 18:14:25 +08:00
Wu Clan b9a9b1efe6 Update the SQL to adapt frontend plugin (#694) 2025-06-26 21:58:35 +08:00
Wu Clan c050f58ee9 Fix the OAuth2 redirect route names (#693) 2025-06-26 21:50:22 +08:00
Wu Clan d47375ae41 Optimize dict create and update logic (#691)
* Optimize dict create and update logic

* Update version
2025-06-25 10:41:09 +08:00
Wu Clan 0602c6144d Update the changelog for v1.5.2 (#690) 2025-06-24 17:40:09 +08:00
Wu Clan c84f0658fb Update the dict pagination query parameters (#689)
* Update the dict pagination query parameters

* Update the dict pagination query parameters

* Update version
2025-06-24 17:34:18 +08:00
Wu Clan a2902bd23a Add built-in plugin missing files (#688) 2025-06-24 17:34:06 +08:00
Wu Clan 69a9b90711 Optimize the zip plug-in file name parsing (#687) 2025-06-24 17:33:57 +08:00
Wu Clan b96402e11d Simplify custom response status codes (#686) 2025-06-24 10:42:15 +08:00
Wu Clan 0bc6f6a719 Update the init test data for SQL scripts (#685)
* Update the init test data for SQL scripts

* Update plugin sql scripts

* fix snowflake id
2025-06-24 10:15:23 +08:00
Wu Clan ebd65c8246 Update dict data label column config (#684) 2025-06-23 22:33:34 +08:00
Wu Clan 234bf708b3 Fix the code with outdated system config (#683) 2025-06-23 22:23:06 +08:00
Dylan 6d5e741d94 Optimize api with semantic HTTP status codes (#681) 2025-06-23 22:18:17 +08:00
Wu Clan f9bfe8f510 Add dictionary type and datas queries (#679) 2025-06-23 10:58:55 +08:00
Wu Clan 408c866dda Update cache cleanup for logout interface (#678)
* Update cache cleanup for logout interface

* Add dependencies of interface
2025-06-22 23:41:48 +08:00
Wu Clan bac41a46f8 Optimize token detection and caching logic (#677) 2025-06-21 20:18:13 +08:00
Wu Clan 8638c26db1 Add the snowflake ID sql script (#675) 2025-06-20 20:31:44 +08:00
Wu Clan 319ba13df1 Optimize routes to better align with RESTful (#673)
* Optimize routes to better align with RESTful

* Add codes endpoint description

* Update jinja templates

* fix typo

* fix sql
2025-06-19 11:06:34 +08:00
Wu Clan 0d1f05d307 Fix some error class import (#672) 2025-06-17 15:30:35 +08:00
Wu Clan e6608d18ce Update the changelog for v1.5.1 (#671) 2025-06-16 21:38:13 +08:00
Dylan 7afd8415cd Add support for snowflake ID primary key (#670)
* fix: 修复PostgreSQL SQL语法错误,将反引号替换为双引号

* feat: 新增雪花算法ID实现

* 优化雪花算法和主键类型

* 修复错误引用

* 添加雪花详情链接

* feat: add snowflake ID parser method

* 修复独立执行异常

* 更新系统时间错误类
2025-06-16 13:34:27 +08:00
Dylan 845f2f0ff8 Fix the postgresql sql script syntax error (#669) 2025-06-15 16:38:48 +08:00
Wu Clan 11d7792c0f Bump sqlalchemy crud plus version to 1.10.0 (#668)
* Bump sqlalchemy crud plus version to 1.10.0

* Update filter style

* Update filters style
2025-06-14 12:35:19 +08:00
Wu Clan 6883ec34c6 Fix the sidebar menu type filtering (#667) 2025-06-13 16:50:53 +08:00
Wu Clan 4c45e7ff27 Update the changelog for v1.5.0 (#664) 2025-06-09 21:36:54 +08:00
Wu Clan 5621daea98 Update the version to 1.5.0 (#663) 2025-06-09 21:32:12 +08:00
Wu Clan 71d168dfea Add data permission in init test sql (#662) 2025-06-09 21:00:49 +08:00
Wu Clan 2d4365189d Update menu sort in init test sql (#661) 2025-06-09 20:27:56 +08:00
Wu Clan 0c28a46e60 Update auth failed default status code (#660)
* Update auth failed default status code

* update init sql
2025-06-09 19:58:06 +08:00
Wu Clan a4c7dae99c Update the plugin download interface permission (#659) 2025-06-09 19:10:48 +08:00
Wu Clan bfda0a226c Update code generate interface permission (#658) 2025-06-09 19:10:40 +08:00
Wu Clan c9bbb105a3 Update data scope and rule to m2m (#657) 2025-06-09 19:10:15 +08:00
Wu Clan b415dc8912 Add OAuth2 user to auto bind a role (#656) 2025-06-08 20:18:24 +08:00
Wu Clan 05b74d71a3 Simplify OAuth2 model and optimize auth service (#655) 2025-06-08 16:51:19 +08:00
Wu Clan 10c0c691a7 Update user email and phone operation logic (#654) 2025-06-08 16:43:51 +08:00
Wu Clan 0bc3786fc6 Update OAuth2 callback interface return (#653) 2025-06-06 20:35:25 +08:00
Wu Clan 8e041ccb1b Update the changelog for v1.4.3 (#651) 2025-06-02 20:43:12 +08:00
Wu Clan a82e9b7b9d Fix data permission condition for filter data (#650) 2025-06-01 00:51:31 +08:00
Wu Clan 7bab8cc636 Update default value for role filter scopes (#649) 2025-05-31 23:53:47 +08:00
Wu Clan 7659b63d88 Add data permission condition for filter data (#648) 2025-05-31 23:40:59 +08:00
Wu Clan 33ebc5723f Add get all data scope rules interface (#647) 2025-05-31 23:09:26 +08:00
Wu Clan 86d9efd785 Update the server startup time to string (#646) 2025-05-31 17:26:07 +08:00
Wu Clan 57ae7f03a6 Refactor routes to better align with RESTful (#645) 2025-05-31 17:01:44 +08:00
Wu Clan 6ee3983846 Fix the filter of query all menus (#642) 2025-05-30 22:48:30 +08:00
Wu Clan f05ed5cdda Fix the menu delete interface arg description (#641) 2025-05-30 18:58:33 +08:00
Wu Clan 187dfa5344 Fix the role update business variables (#640) 2025-05-30 18:56:50 +08:00
Wu Clan 918c8a9d33 Update the changelog for v1.4.2 (#639) 2025-05-29 18:12:45 +08:00
Wu Clan ccfe35ea85 Simplify the user info update business (#638) 2025-05-29 18:09:50 +08:00
yzbf 4a118c166a Fix overwriting existing init files in code generator (#637) 2025-05-28 20:17:18 +08:00
Huyuwei 5f3823cb6d Fix auto-increment id for postgres init data (#632)
* fix: Reset auto-increment values for all tables based on max id

* refactor: Remove auto-increment reset for mysql

* remove unused code
2025-05-28 13:01:44 +08:00
Wu Clan 19ef52da9b Optimize install and build of plugin zip (#636)
* Optimiz install and build of plugin zip

* fix import
2025-05-27 11:46:17 +08:00
Wu Clan 39c3339a8d Optimize role-related data processing performance (#635) 2025-05-26 20:04:13 +08:00
Wu Clan 9f52629dcb Update the captcha invalidation error class (#634) 2025-05-26 19:16:56 +08:00
Wu Clan 1cddd6e2d5 Update non-linked sidebar support (#633)
* Update non-linked sidebar support

* fix typing
2025-05-26 15:52:45 +08:00
Wu Clan ed10e6ff47 Update the changelog for v1.4.1 (#630) 2025-05-25 14:19:41 +08:00
Wu Clan 48574fb50a Update the token decode for logout interface (#629) 2025-05-25 14:16:36 +08:00
Wu Clan ee7fe3087d Update the token check for logout interface (#625) 2025-05-23 22:05:26 +08:00
Wu Clan 270ac3d941 Refactor the system token to online users (#624) 2025-05-23 20:18:47 +08:00
Wu Clan 2f76988076 Add the deepwiki badge to README (#623) 2025-05-23 12:09:04 +08:00
Wu Clan b275481742 Update the menu path and type columns (#622)
* Update the menu path and type columns

* Update build vben5 tree

* Update data
2025-05-22 21:22:24 +08:00
Wu Clan f3f1ceb595 Update the changelog for v1.4.0 (#621) 2025-05-22 14:41:03 +08:00
Wu Clan bcc764142b Update the OAuth2 module to plugin (#620)
* Update the OAuth2 module to plugin

* update create user social param
2025-05-22 14:36:23 +08:00
Wu Clan 9b0d9f8296 Update uv installation in docker deploy (#619) 2025-05-22 14:30:53 +08:00
Wu Clan 63ba7646c1 Update the build plugin api params (#615)
* Update the build plugin api params

* update seek

* fix lint
2025-05-20 19:45:22 +08:00
Wu Clan 1a7734185f Fix non-asyncio nested async IO (#610)
* Fix non-asyncio nested async IO

* Add redis init ping
2025-05-20 18:11:11 +08:00
Wu Clan 8541059d23 Update uninstall and build plugin api method (#614) 2025-05-20 18:10:29 +08:00
Wu Clan 61edc81333 Fix the plugin status update logic (#613) 2025-05-20 17:50:07 +08:00
Wu Clan 8998687adb Fix the task result schema param type (#611) 2025-05-20 15:10:15 +08:00
Wu Clan 85b0294da9 Update the new plugin status to changed (#607)
* Fix cache update after plugin uninstall

* Update new to changed
2025-05-17 09:21:11 +08:00
Wu Clan a8ecb4ae3b Add new plugin status check interface (#606) 2025-05-17 09:06:06 +08:00
Wu Clan a87ff74b56 Update the changelog for v1.3.0 (#605) 2025-05-16 14:16:59 +08:00
Wu Clan 3c5682988d Update the casbin RBAC module path (#604) 2025-05-16 11:49:18 +08:00
Wu Clan 1d32154498 Optimize the zip plugin install logic (#603) 2025-05-15 23:40:33 +08:00
Wu Clan f4290ddb8c Fix the fastapi cli startup event loop (#602) 2025-05-15 22:54:25 +08:00
Wu Clan 1e54b0ba42 Add plugin info config and interfaces (#601)
* Add plugin info config and interfaces

* Remove test data
2025-05-15 20:50:24 +08:00
Wu Clan 27b9cf4c64 Simplify apps and plugins config method (#600) 2025-05-13 13:16:06 +08:00
Wu Clan 87f29e52a1 Update the changelog for v1.2.0 (#598) 2025-05-05 19:46:41 +08:00
Wu Clan 16a0f9646d Update the SQL script for creat tables (#597) 2025-04-28 19:39:33 +08:00
Wu Clan 032364e48e Refactor the data rule to scope rule (#596)
* WIP

* update codes

* update codes

* update filter_data_permission

* Fix schema

* Fix issues
2025-04-28 18:15:52 +08:00
Wu Clan bc5d142920 Optimize the server information retrieval (#595)
* Optimize the server information retrieval

* Update import

* Fix typing
2025-04-28 15:41:53 +08:00
Wu Clan 9b3425d801 Update the default RBAC solution (#593) 2025-04-27 14:54:39 +08:00
Wu Clan 5842127c2b Update code generator table columns (#590)
* Update code generator table columns

* Update func doc
2025-04-24 20:27:08 +08:00
Wu Clan 193eba3c58 Update the changelog for v1.1.2 (#589) 2025-04-23 23:43:56 +08:00
Wu Clan 338e50e255 Update the routing style of the task app (#588)
* Update the routing style of the task app

* Fix prefix
2025-04-23 19:33:36 +08:00
Wu Clan 65500d7a53 Bump fastapi pagination from 0.12.34 to 0.13.0 (#587) 2025-04-23 13:04:09 +08:00
Wu Clan 0833cd5443 Optimize the userinfo cache cleaning logic (#586) 2025-04-23 12:40:27 +08:00
Wu Clan e8c844ead7 Fix cache cleanup when updating role menu (#585) 2025-04-21 17:58:01 +08:00
Wu Clan 1d5f2dc5f0 Fix the condition to query menu by title (#584) 2025-04-21 17:57:12 +08:00
Wu Clanandwu-clan 9a36154564 Update the changelog for v1.1.1 (#583)
This commit was created by changelog-from-release in 'Release changelog' CI workflow

Co-authored-by: wu-clan <wu-clan@users.noreply.github.com>
2025-04-18 23:19:01 +08:00
Wu Clan 5f8ae74ea5 Fix list query in the dict plugin (#582) 2025-04-18 23:16:03 +08:00
Wu Clan da8e7c1b01 Fix the plugin system route injection (#581) 2025-04-18 23:09:25 +08:00
Wu Clan f513b98f15 Update the changelog for v1.1.0 (#580) 2025-04-17 17:54:52 +08:00
Wu Clan 80cef6dde7 Update code generator file and table naming (#579)
* Update code generator file and table naming

* Update column table comment
2025-04-17 17:47:20 +08:00
Huyuwei 2d504f281e Fix avatar url type of update avatar (#575) 2025-04-17 15:02:19 +08:00
Wu Clan fc34c98f0a Update the code generator to plugin (#578)
* Update the code generator to plugin

* Fix get all tables return type
2025-04-17 11:06:43 +08:00
Wu Clan 1cc020efdd Update unique columns in dict models (#577) 2025-04-17 11:06:29 +08:00
Wu Clan bb33e99e33 Update casbin RBAC verify to dynamic import (#576) 2025-04-17 11:06:15 +08:00
Wu Clan 6c70b34ae6 Optimize the file structure of code generator (#574) 2025-04-15 19:54:18 +08:00
Wu Clan 960d9f6215 Update the default value for some functions (#573)
* Update the default value for some functions

* Fix lint
2025-04-14 14:22:05 +08:00
Wu Clan 085f08ec3b Update the changelog for v1.0.5 (#572) 2025-04-09 11:55:49 +08:00
Wu Clan 2a8722f56e Fix the log rule in gitignore (#571) 2025-04-09 11:53:55 +08:00
Wu Clan 4372b9bbc2 Bump fastapi oauth2 from 0.0.1a2 to 0.0.1 (#570) 2025-04-09 11:49:04 +08:00
Wu Clan db81319dc4 Fix the failure hook of celery task (#569) 2025-04-08 12:09:29 +08:00
Wu Clan 644f7a0413 Fix the login password verification (#568)
* Fix the login password verification

* Update the check criteria
2025-04-07 11:04:39 +08:00
Wu Clan b93ff19ebe Update the number of pagination le (#565) 2025-04-03 19:32:26 +08:00
Wu Clan 4c007cb47d Update custom validation error messages (#566) 2025-04-03 19:32:11 +08:00
Wu Clan c9e9a45288 Update the vben5 tree data structure (#564) 2025-04-02 23:18:55 +08:00
Wu Clan d44a5491f5 Update menu and add vben5 compatibility (#563)
* Update menu and add vben5 compatibility

* Update sidebar api description
2025-04-02 22:20:19 +08:00
Wu Clan 289cf5b974 Update dict data and type to plugin (#562) 2025-04-02 14:04:06 +08:00
Wu Clan e207622a70 Update the system config to plugin (#561) 2025-04-02 13:43:24 +08:00
Wu Clan 38d9d9fda5 Add python 3.13 to GitHub ci (#560) 2025-03-28 14:22:45 +08:00
Wu Clan 996895e775 Bump dependencies and pre-commits (#559) 2025-03-28 14:22:20 +08:00
Wu Clan 0675e53f2a Update the changelog for v1.0.4 (#558) 2025-03-28 10:15:11 +08:00
Wu Clan e492cec7d7 Optimize codes and comments with cursor (#550) 2025-03-28 10:08:32 +08:00
PoetryL 92fe1e7554 Fix the query for the sub department (#557) 2025-03-27 23:30:12 +08:00
Wu Clan 7b080600f6 Update volumes of redis in docker compose (#556) 2025-03-26 18:58:06 +08:00
Wu Clan 71f9667bc0 Fix the celery env in docker compose (#555) 2025-03-26 18:34:41 +08:00
Thank CAT 2bf548f8b9 Fix filters for opera log query list (#554) 2025-03-25 09:19:28 +08:00
Wu Clan 15fd4545aa Optimize schemas with model relationships (#552) 2025-03-20 18:46:13 +08:00
Wu Clan 7f6c4ad6da Fix return schema of the config api (#551) 2025-03-20 16:27:44 +08:00
Wu Clan 2be12b4ea4 Fix async install plugin dependencies for windows (#549) 2025-03-17 23:39:10 +08:00
Wu Clan baebdd9870 Optimize dependencies to reduce package size (#548) 2025-03-17 18:41:58 +08:00
Wu Clan d1648bb709 Update git and docker ignore files (#547)
* Update git and docker ignore files

* add __pycache__ dir

* add .python-version
2025-03-17 13:20:21 +08:00
Wu Clan a4a394a18d Optimize the dynamic import of data models (#546) 2025-03-16 15:13:09 +08:00
Wu Clan 951fdc902a Optimize the return of relationship interfaces (#545)
* Optimize the return of relationship interfaces

* Fix gen business detail schema
2025-03-13 18:30:05 +08:00
Wu Clan 18ffa06d70 Updated the backend contribution guide (#544) 2025-03-12 18:15:10 +08:00
Wu Clan d28f152d78 Update the changelog for v1.0.3 (#543) 2025-03-12 14:23:38 +08:00
Wu Clan 1e3e9ea84a Update docker scripts and nginx conf (#542) 2025-03-11 23:26:53 +08:00
Huyuwei 5b42e59d65 Add Aliyun mirror to PyPI index in pyproject.toml (#541)
* Add Aliyun mirror to PyPI index in pyproject.toml

* Update uv.lock

* fix end of files

* Remove UV_INDEX environment variable from Dockerfile
2025-03-11 16:48:30 +08:00
Huyuwei 1882467554 Fix Dockerfile mounts for dependency installation (#540) 2025-03-10 15:34:11 +08:00
Huyuwei f1cd48e9d0 Fix typos in Dockerfile comments (#539) 2025-03-10 15:33:42 +08:00
Wu Clan d1fd84617b Refactor toml and dependencies file dir (#538)
* Refactor toml and dependencies file dir

* fix ci
2025-03-09 23:38:20 +08:00
Wu Clan 03fcf82b26 Update docker scripts in backend README (#537) 2025-03-04 18:34:07 +08:00
Wu Clan f75e1bf3df Update the changelog for v1.0.2 (#536) 2025-03-01 16:01:41 +08:00
Wu Clan 554a6f59bd Add plugin related interfaces (#535)
* Add plugin related interfaces

* Update interface auth

* Add build plugin zip service
2025-03-01 15:58:41 +08:00
Wu Clan 68d41cf028 Add log module root and output levels (#534) 2025-02-27 15:38:41 +08:00
Wu Clan 5defeca590 Fix celery async task worker pool (#533)
* Fix celery async task worker pool

* Update celery start scripts

* fix lint
2025-02-27 15:38:10 +08:00
Wu Clan 4d94418124 Update the changelog for v1.0.1 (#532) 2025-02-26 15:53:25 +08:00
Huyuwei 6c258f056a Optimize Dockerfile for faster builds (#526) 2025-02-26 14:45:48 +08:00
Wu Clan 87af1f1319 Fix the sql script for init data (#530) 2025-02-25 21:42:03 +08:00
Wu Clan 3f9badd4b9 Add sqlalchemy connection pool config (#529) 2025-02-25 15:46:10 +08:00
Wu Clan 06de770c4d Add async attrs for sqla mapped base (#528) 2025-02-25 15:43:35 +08:00
Huyuwei b5948441ac Add missing volume config for docker deploy (#525) 2025-02-25 15:43:13 +08:00
Wu Clan acbab69f12 Update the changelog for v1.0.0 (#524) 2025-02-24 21:39:49 +08:00
Wu Clan 1f8ac4953e Add changelog ci for github release 2025-02-24 21:17:56 +08:00
Wu Clan 1d981ec159 Update the built-in features in README (#519) 2025-02-24 12:48:46 +08:00
Wu Clan b0f675f697 Delete the model redundancy level field (#518) 2025-02-24 12:30:08 +08:00
Wu Clan 6d44f807a7 Update loguru and deploy log config (#517)
* Update loguru config

* Update config

* fix log level

* Update deploy log config
2025-02-24 12:17:05 +08:00
Wu Clan 9602e28429 Add local file upload interfaces (#489)
* Add the alibaba cloud oss plugin

* Update oss plugin struct

* Add local upload
2025-02-21 20:18:35 +08:00
Wu Clan 440f324a3d Update the logic for create new token (#516) 2025-02-17 16:56:45 +08:00
Wu Clan 5e7a4e1e74 Update casbin rbac verify to plugin (#513)
* Update casbin rbac verify to plugin

* Update requirements

* move sys api to casbin plugin

* Update plugin toml and parse

* Fix plugin router inject

* Update api prefix to apis
2025-02-17 13:41:11 +08:00
Wu Clan 4d13424b30 Add plugin requirements auto install functions (#514) 2025-02-15 19:58:49 +08:00
Wu Clan 860c96d0ee Delete the threads in gunicorn config (#512) 2025-02-14 13:22:34 +08:00
Wu Clan d9985f9d4d Add plugin system and notice plugin (#503)
* Update system notice to plugin

* Add plugin model alembic support

* update plugin conf

* add plugin route injection

* update plugin route inject

* fix and optimize plugin router inject
2025-02-13 21:19:49 +08:00
Wu Clan 1e0b04006b Fix OAuth2 service user last login time (#510) 2025-02-11 18:10:38 +08:00
Wu Clan d419343939 Fix OAuth2 service user last login time (#509) 2025-02-11 17:57:05 +08:00
Wu Clan eb3fd55321 Fix OAuth2 service register user args (#508) 2025-02-11 17:48:25 +08:00
Wu Clan 4aa4301abd Fix the user pagination api return schema (#507)
* Fix the user pagination api return schema

* lint
2025-02-11 14:28:02 +08:00
Wu Clan 1d1a917580 Fix fastapi config variable naming and type (#506) 2025-02-08 11:47:45 +08:00
Wu Clan 0255283577 Fix the return datetime data encoder (#505) 2025-02-07 12:19:32 +08:00
Wu Clan 2df1f0f358 Bump dependencies and pre-commits (#504) 2025-02-07 12:09:25 +08:00
Wu Clan 52816dd4b4 Fix opera log of non-dict request body (#501) 2025-01-25 10:29:44 +08:00
Wu Clan 20e873b6e5 Fix casbin policy api return schema (#500) 2025-01-24 21:54:20 +08:00
Wu Clan ebe710fac2 Update menu field show to display (#498) 2025-01-23 22:46:05 +08:00
Wu Clan e588913d54 Fix current user info detail schema (#499) 2025-01-23 22:31:39 +08:00
Wu Clan 1c46e2480c Fix return schema of user me api (#497) 2025-01-23 21:52:16 +08:00
Wu Clan 3b65679fb0 Add token related interfaces (#495) 2025-01-23 13:12:46 +08:00
Wu Clan 7553ccf14d Update return schema of query interface (#492)
* Update return model of query interface

* Update tree data return type hints

* Update code generation and task return types

* Fix inconsistency between ORM query result and schema
2025-01-18 23:33:37 +08:00
qhp13654398483 cf693cdce6 Fix serialization when pagination is empty (#491)
* bug fix

* update codes
2025-01-18 20:47:31 +08:00
Wu Clan 3fe0023a0a Update redocs arg and url to redoc (#493) 2025-01-17 16:06:30 +08:00
Wu Clan 13cee674a7 Add response model include data schema (#490)
* Add response model include data schema

* Fix paging data usage

* optimize annotations
2025-01-17 12:39:24 +08:00
dividduang 64ee74bb38 Add system notice interface (#487) 2025-01-14 12:45:33 +08:00
Wu Clan 1e855dbb86 Optimize docker deploy settings and scripts (#486) 2025-01-13 17:33:54 +08:00
Wu Clan e0606d115c Update the route version define location (#485)
* Update the route version define location

* fix lint
2025-01-10 14:00:26 +08:00
Wu Clan 8b159536ce Fix user cache when updated user role (#483) 2025-01-03 18:53:26 +08:00
Wu Clan 2279ff7c58 Bump msgspec from 0.18.6 to 0.19.0 (#482) 2024-12-28 17:49:28 +08:00
Wu Clan 3f6c43e15d Update opera middleware request args parse (#481) 2024-12-27 18:27:05 +08:00
Wu Clan 6c1f68ec00 Update opera log cost time precision (#479) 2024-12-25 22:09:27 +08:00
Meepoljdx 78dad3021a Add postgresql database support (#475)
* feat: add postgresql db supports

* change: change mysql conn str create way

* fix: Modify the default alembic migration file to meet multi-database support

* Update settings and lint

* update models

* Simplify database config

* Simplify the get db method

* Update create db url

* Updated model type adaptation

* Update sql scripts

* Fix models type

* Adaptation to postgresql code generation

* Update README.md

* Fix alembic file template

* Update docker scripts
2024-12-25 22:04:04 +08:00
Wu Clan 4f59f459a0 Remove data scope in the role model (#478) 2024-12-24 17:29:17 +08:00
Wu Clan 58b24b62ac Fix login log parameter error in task (#476) 2024-12-13 16:40:48 +08:00
Wu Clan a9b9b58290 Fix and update alembic env and ini (#474) 2024-12-11 19:55:46 +08:00
Wu Clan f90b4667c3 Fix data rule expression column comment (#473) 2024-12-09 12:21:40 +08:00
Wu Clan 3b2b45d6a5 Optimized user auth for auth service (#472) 2024-12-07 14:03:30 +08:00
Wu Clan 8c04bcb981 Add the project logo to README (#469)
* Add the project logo to README

* update
2024-12-01 23:43:04 +08:00
Wu Clan 7c4e288fd6 Simplify data rule and remove type (#468)
* Simplify data rule and remove type

* fix lint

* fix user info cache
2024-11-27 14:09:57 +08:00
Wu Clan 1838b63b11 Fix schema type of user role rule (#467)
* Fix schema type of user role rule

* Fix sql scripts
2024-11-24 16:42:40 +08:00
dependabot[bot] 6d5d1a48f3 Bump tornado from 6.4.1 to 6.4.2 in /backend (#466)
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to 6.4.2.
- [Changelog](https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2)

---
updated-dependencies:
- dependency-name: tornado
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-23 21:35:05 +08:00
Wu Clan bd45022a59 Update role-based data permissions (#465)
* Add department data operation permissions

* Update sys models

* Add role department many-to-many relationship

* Format RBAC code

* update codes

* add update role depts api

* add comments

* Update the implementation

* update dept arg

* debug front

* Update the model file naming

* Refactor the data perms

* Add data permission rule

* Optimize the details

* Implement data filtering

* finish
2024-11-23 21:23:52 +08:00
Wu Clan b29a3cbfb7 Update user password encryption method (#463) 2024-11-18 14:07:05 +08:00
Wu Clan a15693cc26 Fix celery service functions error (#462) 2024-11-16 17:13:29 +08:00
Wu Clan 5e60d9c248 Update api body params to schema (#461) 2024-11-16 11:07:40 +08:00
Wu Clan 12d7ec8174 Update code generator API file structure (#460) 2024-11-15 17:03:35 +08:00
Wu Clan 601a6514a8 Update some service class invocations (#459) 2024-11-15 17:02:52 +08:00
Wu Clan fc5a0f9dfa Update the celery configuration and tasks (#458)
* Update the celery configuration and tasks

* fix message notifications
2024-11-15 14:29:02 +08:00
Wu Clan 576ed77d36 Fix code generation file missing (#457) 2024-11-13 19:26:17 +08:00
Wu Clan 06cbe56201 Fix typo in contribution description (#456) 2024-11-13 13:40:37 +08:00
Wu Clan 87eec419e6 Update multiple version dependency specifiers (#454) 2024-11-12 19:52:42 +08:00
Wu Clan 0e7cef5b6c Update system config to dynamic (#447)
* Update system config to be dynamic

* update the configuration interface

* custom parameters are allowed

* update apis

* add more code

* finish

* fix bugs
2024-11-10 07:30:05 +08:00
Wu Clan 5c0b107142 Update singleton pattern class typing (#452) 2024-11-08 12:16:13 +08:00
Wu Clan 0cfeeb4e45 Bump sqlalchemy crud plus version to 1.5.0 (#450) 2024-11-07 13:42:40 +08:00
Wu Clan aabb72138a Update the usage documentation in README (#449) 2024-11-07 11:30:00 +08:00
Wu Clan 0a1f0a90b2 Add dependency-groups by PEP 735 (#444) 2024-11-03 18:56:07 +08:00
Wu Clan 493c2b2545 Add asynchronous socketio application server (#437)
* Add asynchronous socketio application server

* fix uv lock

* Update cors

* fix socket app
2024-11-02 00:12:01 +08:00
Wu Clan 0810647c48 Update project manager pdm to uv (#440)
* Update project manager pdm to uv

* Fix ci venv

* Fix ci venv source
2024-10-12 18:09:41 +08:00
Wu Clan a3d079cfdf Optimize the serialize return of SQLA select (#436) 2024-10-10 12:11:05 +08:00
Wu Clan c22d1394e7 Update interactive link descriptions (#434) 2024-10-08 22:12:46 +08:00
Wu Clan 03add792a1 Bump redis from 5.0.1 to 5.1.0 (#433)
* Bump redis from 5.0.1 to 5.1.0

* fix requirements.txt
2024-09-28 15:05:18 +08:00
Wu Clan ab4495ce65 Optimize and normalize the code generator (#430) 2024-09-24 13:50:02 +08:00
Wu Clan 1d3b0e7d13 Update official documentation link to README (#429) 2024-09-22 15:56:27 +08:00
Wu Clan 36a458b957 Fix the OAuth2 service login log task (#428) 2024-09-22 12:54:31 +08:00
Wu Clan 23d9d17d9f Fix pydantic field and model validator (#427) 2024-09-21 17:59:52 +08:00
Wu Clan 8da993655b Add request state middleware (#426)
* Add request state middleware

* Fix linux do OAuth2 redirect uri
2024-09-21 17:57:27 +08:00
Wu Clan 6792bf9954 Fix the missing OAuth2 interface parameters (#425) 2024-09-20 20:12:13 +08:00
Wu Clan 6cf30b3c5d Update synchronous function calls in JWT (#423) 2024-09-18 22:24:05 +08:00
Wu Clan eb23d1b5f9 Update multi login sync update refresh tokens (#422) 2024-09-18 22:23:40 +08:00
Wu Clan 29ba40ef32 Update JWT errors class import (#421) 2024-09-13 12:02:20 +08:00
Wu Clan 02f65abf74 Fix for create new token cache delete (#420) 2024-09-13 12:01:49 +08:00
Wu Clan f5b7de6910 Optimize the internal implementation of serializers (#419) 2024-09-11 16:40:05 +08:00
Wu Clan ac8e4a0406 Fix the gen model template formatting (#416) 2024-09-10 16:26:23 +08:00
Wu Clan b6821612af Fix exception logging in opera log (#417) 2024-09-10 16:19:19 +08:00
Wu Clan 8783d74cc7 Fix log output and logging levels (#414)
* Fix log output and logging levels

* update record order
2024-09-09 23:14:56 +08:00
Wu Clan 5a52871e7e Optimize exception info opera log record (#413) 2024-09-09 22:52:05 +08:00
Wu Clan 90b2cca984 Bump pydantic from 2.8.1 to 2.9.1 (#412) 2024-09-09 20:46:10 +08:00
Wu Clan 9c37297858 Update the global exception log stack (#406)
* Update the global exception log stack

* del todo
2024-09-09 15:56:38 +08:00
Wu Clan 1f95a776f0 Add trace ID to exception handlers (#411) 2024-09-09 15:46:13 +08:00
Wu Clan b13410c7d1 Optimize the naming of setting params (#410) 2024-09-09 11:50:22 +08:00
Wu Clan 9aa2d7b593 Add request trace ID record (#409)
* Fix cookie expiration time zone

* Fix the loss of cross-domain cookies

* Add request trace ID record

* Fix the dependency lock file
2024-09-09 10:43:34 +08:00
Wu Clan 1d956cace4 Fix cookie expiration time zone (#408)
* Fix cookie expiration time zone

* Fix the loss of cross-domain cookies
2024-09-08 17:33:51 +08:00
Wu Clan 4f5c5c28e6 Fix server monitor io blocking (#404) 2024-09-05 22:46:31 +08:00
Wu Clan 24528e6050 Updated refresh token storage logic (#403) 2024-09-05 21:37:25 +08:00
Wu Clan 7853233ddb Update the interactive link address (#402) 2024-09-04 20:06:25 +08:00
Wu Clan 51f3011947 Update code generate comment column format (#399) 2024-09-03 14:40:22 +08:00
Wu Clan 87432a74c2 Fix the user permissions update services (#397) 2024-09-01 20:23:25 +08:00
Wu Clan e19ae09966 Fix OAuth2 user query conditions (#396) 2024-09-01 20:22:58 +08:00
Wu Clan 8765f833d2 Fix code generation to new features (#393) 2024-08-31 18:08:24 +08:00
Wu Clan c22202eac4 Update the README announcement to note (#390)
* Update the README announcement to note

* delete English README some content
2024-08-28 10:30:26 +08:00
Wu Clan a698373064 Bump sqlalchemy crud plus version to 1.3.0 (#388) 2024-08-25 23:02:13 +08:00
Wu Clan b43c08b5d2 Update redis cache prefix separator to : (#387)
* Update redis cache prefix separator to `:`

* Update refresh token redis prefix
2024-08-22 12:28:05 +08:00
Wu Clan 2002068d81 Fix logging when a login error occurs (#386) 2024-08-22 12:26:13 +08:00
六月andliuyue bb0d7f3f56 Fix arm system cpu frequency retrieval (#385)
* Fix: Disable CPU frequency retrieval on macOS systems with Apple Silicon (closes #384)

* Fix: Solve the single quote problem

---------

Co-authored-by: liuyue <yshan2028@gmail.com>
2024-08-21 21:37:26 +08:00
Wu Clan c743182211 Optimize serialization and jwt performance (#382)
* Optimize serialization and algorithm performance

* Fix get tree data to async

* Removed CPU-intensive multi-threaded execution

* Update pdm pre commit to 2.18.0

* Add jwt user info cache

* Update ci

* Fix ci

* Fix redis delete prefix

* Adapt to JWT user instance

* Update jwt middleware token parse
2024-08-17 09:26:16 +08:00
Wu Clan 22586dd947 Add directory tree and update app notes (#380) 2024-08-13 10:45:54 +08:00
Wu Clan f61dc4fc84 Update user and auth error message (#379) 2024-08-11 21:54:46 +08:00
Wu Clan 144d704a5d Fix code generation model create and update (#378) 2024-08-11 21:32:09 +08:00
Wu Clan f30a18844e Remove current menu status auth (#374) 2024-08-11 21:00:27 +08:00
Wu Clan 043a622e67 Update code generate download api auth (#376) 2024-08-04 19:43:03 +08:00
Wu Clan a602dff739 Update some code generation api and params (#373)
* Update some code generation api and params

* Update get business api query

* Add get business models api

* Fix get business models api serialize

* Add get generate code path api

* Update download method post to get

* Add get model detail api

* Fix crud model method

* Fix get model api return

* Fix update model service

* Update create update model service

* Add code generate sql

* Add code generate api permissions

* Update code gen path join
2024-08-04 15:24:09 +08:00
Wu Clan 863dfed4ec Add custom code template pathname config (#372) 2024-07-28 12:38:05 +08:00
Wu Clan ceb8fb1cb2 Update code generate business model datetime column (#371) 2024-07-28 11:58:46 +08:00
Wu Clan 21fed49d7b Update access log cost time style (#370) 2024-07-27 12:04:22 +08:00
Wu Clan c9be643e76 Update create new token function return type (#369)
* Update create new token function return type

* update args input
2024-07-27 12:03:33 +08:00
Wu Clan 7287093772 Update operation log middleware info reading (#368)
* Fix operation log middleware summary reading

* update ua info and ip info

* update opera middle functions

* move dataclasses.py

* fix lint
2024-07-27 11:00:36 +08:00
Wu Clan 2112523b4c Fix celery asynchronous task execution (#367) 2024-07-26 19:00:38 +08:00
Wu Clan 34506c3039 Add page to display dynamic configuration (#365)
* Add page to display dynamic configuration

* update config data and api

* add config route

* update config api route

* update config api route

* fix config schema

* update config services
2024-07-25 19:11:06 +08:00
Wu Clan f94c2d7948 Fix code auto-generated model creation (#364) 2024-07-23 17:58:23 +08:00
Wu Clan 294d7ae9e9 Update crud user staff field logic (#363) 2024-07-23 15:48:40 +08:00
Wu Clan a0d47733a9 Bump fast captcha version to 0.3.2 (#362) 2024-07-22 23:31:00 +08:00
Wu Clan 581bed2a33 Update oauth2 route naming and return (#361)
* Update oauth2 route naming and storage

* Update some arg naming

* Update oauth2 redirect uri

* adapts to the front-end oauth2
2024-07-22 13:47:43 +08:00
Wu Clan f9c4028c60 Update the docker-compose deployment script (#360)
* Update the docker-compose deployment script

* fix typo

* Update docker compose fba_ui conf

* Update the docker compose fba_ui conf

* Add docker compose image repository name

* Update docker compose fba_ui comment
2024-07-21 09:55:39 +08:00
Wu Clan 9752ce8e92 Fix alembic migration failure caused by model (#359) 2024-07-18 20:22:34 +08:00
Wu Clan 3cfc95cb02 Add code generator to create init files (#358) 2024-07-18 18:05:56 +08:00
Wu Clan 3d12c3270e Fix gen model and schema template formatting (#356)
* Fix gen model column default value

* Update gen templates and schema_name storage

* Fix lint
2024-07-17 17:06:55 +08:00
Wu Clan 812b8a0fb7 Update code generation model column type storage (#352) 2024-07-12 21:21:01 +08:00
Wu Clan e5262f0b32 Fix model template conditional syntax (#351)
* Fix model template conditional syntax

* Update README
2024-07-12 16:42:30 +08:00
Wu Clan 42ee5d7355 Add code generator README document (#349)
* Update and enable access log middleware

* Add code generator README document

* additional doc
2024-07-10 17:08:15 +08:00
Wu Clan 5a283b1c3c Update and enable access log middleware (#348) 2024-07-10 16:47:23 +08:00
Wu Clan f9ec9d5d56 Add code generator app (#318)
* Add code generator app

* Define the generator interfaces

* Update the name of the template files

* update lock hash

* update route and schemas

* update gen code structure

* add some gen codes

* upgrade SQLAlchemy to release

* update template util

* Fix lint error

* Add template rendering code

* Add gen model code

* Fix gen model table

* Update business model column naming

* Update any route

* Fix api ninja args

* Update gen business model

* Fix interface services logic

* Fix template render

* Add more code template

* Add model auto gen template

* Fix model type map style

* Update schema and model templates

* Update gen_model schema

* Fix model template if

* Update have_datetime_column field from gen_model to gen_business table

* Update the name of the zip

* Update model template vars

* Add download generate code

* Add preview code encoding

* Update service code template

* fix lint

* Update github auth tag

* Update requirements

* Add code generation write

* Update generate interface and zip conf

* Optimize templates details

* Update model relation column conf

* Add import tables module

* Add get all databases interface

* Fix get tables interface

* Bump pydantic to 2.8.1

* Upgrading lint dependencies

* Add import business and model implement

* Split import database execution code

* Update text SQL execution to prevent SQL injection

* Optimize schema name generation

* clean code

* Update create tables sql scripts

* Update import sql query conditions

* Add todo comments

* Mark business creation interface

* Update features in README
2024-07-10 12:12:29 +08:00
obrua 3b060c73b2 Refactor global log default handler (#347)
* 🦄 refactor: optimize log output

Unified log output using the loguru logging library

* 🌈 style: pre-commit fix

* 🌈 style: pre-commit fix

* Clean up and update some code

* Update default log output style
2024-07-09 18:40:25 +08:00
Wu Clan 85c16de32b Fix user social binding query (#344) 2024-07-08 16:27:26 +08:00
Wu Clan 9aa74afff1 Add Linux Do OAuth2 login (#343)
* Add Linux Do OAuth2 login

* Adapt to linux do oauth2 email field

* Fix get user email logic

* Fix lint
2024-07-07 17:39:54 +08:00
Wu Clan f21af0c3c3 Update app route definition rules (#341) 2024-07-02 09:29:22 +08:00
Wu Clan af101d57fe Update the tree structure traversal algorithm (#338) 2024-07-02 08:49:40 +08:00
Wu Clan 1ad5482a22 Restore the main startup of fastapi service (#336) 2024-06-16 14:09:14 +08:00
Wu Clan 805a5a4e96 Add test account to README (#330) 2024-06-08 23:47:35 +08:00
Wu Clan 08a8acb9ea Update the opera log middleware task (#326)
* Update the opera log middleware task

* Delete task await
2024-06-03 12:58:05 +08:00
Wu Clan dd474cdc08 Add interface fast response method (#327) 2024-06-03 12:46:05 +08:00
Wu Clan 84c16bc983 Delete the gzip middleware to improve performance (#325) 2024-06-03 12:45:42 +08:00
Wu Clan 2589ddcdf6 Upgrade and update fastapi service startup (#319) 2024-05-04 22:39:09 +08:00
Wu Clan eb8dfda8fa Update CRUDBase to sqlalchemy-crud-plus (#317)
* Update CRUDBase to dependent package

* Update sqla crud plus to 0.0.2
2024-04-27 23:07:40 +08:00
Wu Clan 9edb72d065 Fix missing data from alembic migration (#312) 2024-04-12 11:38:18 +08:00
Wu Clan 406116a230 Add backend scripts description (#309) 2024-03-31 19:48:58 +08:00
Wu Clan 8a165ec181 Fix English README.md update date (#308) 2024-03-22 18:24:03 +08:00
Wu Clan 5e438c685d Refactor the backend architecture (#299)
* define the basic architecture

* Update script and deployment file locations

* Update the route registration

* Fix CI download dependencies

* Updated ruff to 0.3.3

* Update app subdirectory naming

* Update the model import

* fix pre-commit pdm lock

* Update the service directory naming

* Add CRUD method documents

* Fix the issue of circular import

* Update the README document

* Update the SQL statement for create tables

* Update docker scripts and documentation

* Fix docker scripts

* Update the backend README.md

* Add the security folder and move the redis client

* Update the configuration item

* Fix environment configuration reads

* Update the default configuration

* Updated README description

* Updated the user registration API

* Fix test cases

* Update the celery configuration

* Update and fix celery configuration

* Updated the celery structure

* Update celery tasks and api

* Add celery flower

* Update the import style

* Update contributors
2024-03-22 18:16:15 +08:00
Wu Clan a98621b40d Update the README.md branch prompt (#302) 2024-03-21 12:13:00 +08:00
Wu Clan 01ee7deeff Prepare to lock the legacy branch (#301)
* Prepare to lock the legacy branch

* Update README.md

* Fix typo

* Fix the docker environment file
2024-03-21 10:56:12 +08:00
Wu Clan 3b64aeb97b Add OAuth 2.0 authorization login (#293)
* [WIP] Add OAuth 2.0 authorization login

* Add social user relationship table

* Update social user relationship table back_populates

* Add OAuth 2.0 related interface

* Automatically redirect authorization addresses

* Update OAuth2 authorization to GitHub

* Add implementation code

* fix the callback interface return

* fix typo

* fix the api return

* fix imports

* Fix logic for creating system users and social tables

* Fix user information storage

* Add OAuth2 source link

* remove unnecessary db refresh

* remove the front end docker-compose annotation
2024-03-15 13:46:43 +08:00
Wu Clan 594efb5e96 Add the repository star map (#296) 2024-03-15 10:41:07 +08:00
Wu Clan 8af4336428 Update interface file directory level (#295)
* Update interface file directory level

* fix ruff format
2024-02-25 20:48:25 +08:00
Wu Clan 115b6f0b30 Update the response status code in exception handlers (#292) 2024-02-20 10:40:21 +08:00
Wu Clan 2bad404913 Add the front-end docker-compose script (#283)
* Add the front-end docker-compose script

* Update the file content location

* fix typo

* Update the default nginx docker compose

* Add an online demo document
2024-02-03 20:41:41 +08:00
Wu Clan 7e633dbdd5 Add the pdm project manager (#282)
* Add the pdm project manager

* Update the interactive links

* fix typo
2024-02-01 17:37:29 +08:00
Wu Clan 2ae020e70a Restore the Github ci workflows (#281)
* Restore the Github ci workflows

* upgrade actions version
2024-01-31 10:37:41 +08:00
Wu Clan 94cbc629cd Fix custom validator exception serialization in dev mode (#278)
* Fix custom validator exception serialization in dev mode

* Fix formatting judgment conditions
2024-01-24 10:17:20 +08:00
Wu Clan 4f1d1c2094 Add custom email string type (#277) 2024-01-24 10:17:04 +08:00
Wu Clan cd96d2f5cd Update dao and service instantiation styles (#276)
* Update dao and service instantiation styles

* Fix auth_service use
2024-01-22 10:22:13 +08:00
Wu Clan 576160129a Update interface coding style (#275) 2024-01-20 19:58:19 +08:00
Wu Clan 754b9d9edc Fix dept and menu parent id update logic (#274) 2024-01-20 19:58:06 +08:00
Wu Clan b5c656f308 Update sponsor links and FUNDING (#273) 2024-01-18 23:06:42 +08:00
Wu Clan a41b1673e5 Update schemas naming style (#272) 2024-01-18 19:27:41 +08:00
Wu Clan a22aaa0a3e Attempt to optimize serialization performance (#266)
* Attempt to serialize performance optimization

* Add casbin service functions return type

* update comments
2024-01-11 20:15:03 +08:00
Wu Clan 5c7d6659cf Reconstruct RBAC authentication logic (#264)
* Reconstruct RBAC authentication logic

* fix typo

* Migrate casbin sqla Adapter to redis

* Delete casbin model conf file

* Add permission dependencies

* Add request permission depends on execution condition

* Update openapi authorization method

* Add request permission identity

* Add request permission dependency description

* Migrate casbin redis adapter to sqla

* Update menu model and add function

* Fix menu permission identification

* Update user partial interface permissions

* Update menu table SQL

* Add role menu permission description to README

* fix README typo

* Simplify permission dependency injection

* Fix menu authorization store

* Fix interface permission dependency order

* Update role menu permission flag

* Update the background permission logic of the interface
2024-01-08 09:35:47 +08:00
Wu Clan f39d11eb9d Fix use request.form() in middleware (#260)
* Fix request.form() in middleware

* resume global exception handling

* update the order in which data is processed

* Add demo interface for uploading files

* fix path params
2024-01-03 22:23:56 +08:00
Wu Clan 227221ff67 Clean up outdated pydantic dict methods (#262) 2024-01-02 22:05:20 +08:00
Wu Clan 27d229c250 Add the project status page to the README (#259) 2023-12-23 18:24:52 +08:00
Wu Clan 754498be0b Add pydantic-v2 migration reminder (#257)
* Add pydantic-v2 migration reminder

* Fix format rendering

* Beautify the date format

* Add celebratory content
2023-12-21 00:38:59 +08:00
Wu Clan 7bfc7908ab Migrate to pydantic-v2 (#246)
* update the schema to pydantic-v2

* upgrade the ruff version in pre-commit

* update pagination to p-v2

* update encoder to p-v2

* update response_schema to p-v2

* update exception_handler to p-v2

* update config to p-v2

* update crud base to p-v2

* Migrate the JSON Encoder to the official one to reduce maintenance costs

* updated readme description

* restore the bcrypt compatible version

* fix validation error log code return format

* Fix manual processing method for GetCurrentUserInfo

* Integrate pydantic validate exceptions and user exceptions

* Fix validation exception handler

* add custom open response code

* Add a stand-alone assertion error handler

* Restore todo deleted by v1 branch to v2

* Add email and phone number types
2023-12-20 23:56:37 +08:00
460 changed files with 22303 additions and 7608 deletions
+9
View File
@@ -0,0 +1,9 @@
__pycache__/
.git/
.github/
.idea/
.vscode/
venv/
.venv/
.ruff_cache/
.pytest_cache/
+1
View File
@@ -0,0 +1 @@
custom: https://wu-clan.github.io/sponsor/
+21
View File
@@ -0,0 +1,21 @@
name: Release changelog
on:
push:
tags:
- v*
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: master
- uses: rhysd/changelog-from-release/action@v3
with:
file: CHANGELOG.md
pull_request: true
github_token: ${{ secrets.GH_TOKEN }}
+34
View File
@@ -0,0 +1,34 @@
name: ci
on:
push:
branches:
- master
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
name: lint ${{ matrix.python-version }}
strategy:
matrix:
python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14' ]
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
run: |
uv sync --only-group lint
- name: Run lint
run: |
source .venv/bin/activate
chmod 755 backend/scripts/lint.sh
./backend/scripts/lint.sh
+5 -6
View File
@@ -1,10 +1,9 @@
__pycache__/
.idea/
.env
.vscode/
.cursor/
.DS_Store
venv/
.mypy_cache/
backend/app/log/
backend/app/alembic/versions/
backend/app/static/media/
.venv/
.python-version
.ruff_cache/
.pytest_cache/
+15 -7
View File
@@ -1,19 +1,27 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v6.0.0
hooks:
- id: check-added-large-files
- id: end-of-file-fixer
- id: requirements-txt-fixer
- id: check-yaml
- id: check-toml
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.1.6
rev: v0.14.0
hooks:
- id: ruff
- id: ruff-check
args:
- '--config'
- '.ruff.toml'
# https://github.com/astral-sh/ruff-pre-commit/issues/64
- '--fix'
- '--unsafe-fixes'
- id: ruff-format
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.9.0
hooks:
- id: uv-lock
- id: uv-export
args:
- '-o'
- 'requirements.txt'
- '--no-hashes'
+153 -21
View File
@@ -1,42 +1,174 @@
line-length = 120
target-version = "py310"
cache-dir = "./.ruff_cache"
[per-file-ignores]
"backend/app/api/v1/*.py" = ["TCH"]
"backend/app/models/*.py" = ["TCH003"]
"backend/app/**/__init__.py" = ["F401"]
"backend/app/tests/*.py" = ["E402"]
preview = true
fix = true
unsafe-fixes = true
show-fixes = true
required-version = ">=0.13.0"
[lint]
select = [
"E",
"F",
"I",
"W505",
"PT018",
"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",
"PGH004",
"PLE1142",
"RUF100",
"F404",
"TCH",
"UP007"
"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",
"TRY400",
"TRY003",
"TRY301"
]
[lint.per-file-ignores]
"**/model/*.py" = ["TC003"]
"backend/common/socketio/server.py" = ["ANN001"]
"backend/common/exception/exception_handler.py" = ["ANN202","RUF029"]
[lint.flake8-pytest-style]
mark-parentheses = false
parametrize-names-type = "list"
parametrize-values-row-type = "list"
parametrize-values-type = "tuple"
parametrize-values-type = "list"
[lint.flake8-quotes]
inline-quotes = "single"
[lint.flake8-type-checking]
runtime-evaluated-base-classes = ["pydantic.BaseModel", "sqlalchemy.orm.DeclarativeBase"]
[lint.flake8-unused-arguments]
ignore-variadic-names = true
[lint.isort]
case-sensitive = true
lines-between-types = 1
order-by-type = true
[lint.pylint]
allow-dunder-method-names = ["__tablename__", "__table_args__"]
[format]
docstring-code-format = true
preview = true
quote-style = "single"
+902
View File
@@ -0,0 +1,902 @@
<a id="v1.9.0"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.3...v1.9.0
[Changes][v1.9.0]
<a id="v1.8.3"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.2...v1.8.3
[Changes][v1.8.3]
<a id="v1.8.2"></a>
# [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)
## 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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.1...v1.8.2
[Changes][v1.8.2]
<a id="v1.8.1"></a>
# [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)
## 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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.0...v1.8.1
[Changes][v1.8.1]
<a id="v1.8.0"></a>
# [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)
## New Contributors
* [@IAseven](https://github.com/IAseven) made their first contribution in [#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
[Changes][v1.8.0]
<a id="v1.7.0"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.6.0...v1.7.0
[Changes][v1.7.0]
<a id="v1.6.0"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.2...v1.6.0
[Changes][v1.6.0]
<a id="v1.5.2"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.1...v1.5.2
[Changes][v1.5.2]
<a id="v1.5.1"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.0...v1.5.1
[Changes][v1.5.1]
<a id="v1.5.0"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.3...v1.5.0
[Changes][v1.5.0]
<a id="v1.4.3"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.2...v1.4.3
[Changes][v1.4.3]
<a id="v1.4.2"></a>
# [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)
## 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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.1...v1.4.2
[Changes][v1.4.2]
<a id="v1.4.1"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.0...v1.4.1
[Changes][v1.4.1]
<a id="v1.4.0"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.3.0...v1.4.0
[Changes][v1.4.0]
<a id="v1.3.0"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.2.0...v1.3.0
[Changes][v1.3.0]
<a id="v1.2.0"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.2...v1.2.0
[Changes][v1.2.0]
<a id="v1.1.2"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.1...v1.1.2
[Changes][v1.1.2]
<a id="v1.1.1"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.0...v1.1.1
[Changes][v1.1.1]
<a id="v1.1.0"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.5...v1.1.0
[Changes][v1.1.0]
<a id="v1.0.5"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.4...v1.0.5
[Changes][v1.0.5]
<a id="v1.0.4"></a>
# [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)
## 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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.3...v1.0.4
[Changes][v1.0.4]
<a id="v1.0.3"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.2...v1.0.3
[Changes][v1.0.3]
<a id="v1.0.2"></a>
# [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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.1...v1.0.2
[Changes][v1.0.2]
<a id="v1.0.1"></a>
# [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)
## New Contributors
* [@huyuwei1996](https://github.com/huyuwei1996) made their first contribution in [#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
[Changes][v1.0.1]
<a id="v1.0.0"></a>
# [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)
## 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)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/commits/v1.0.0
[Changes][v1.0.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
<!-- Generated by https://github.com/rhysd/changelog-from-release v3.9.0 -->
+68
View File
@@ -0,0 +1,68 @@
# Select the image to build based on SERVER_TYPE, defaulting to fastapi_server, or docker-compose build args
ARG SERVER_TYPE=fastapi_server
# === Python environment from uv ===
FROM ghcr.io/astral-sh/uv:python3.10-bookworm-slim AS builder
# Used for build Python packages
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends gcc python3-dev \
&& rm -rf /var/lib/apt/lists/*
COPY . /fba
WORKDIR /fba
# Configure uv environment
ENV UV_COMPILE_BYTECODE=1 \
UV_NO_CACHE=1 \
UV_LINK_MODE=copy \
UV_PROJECT_ENVIRONMENT=/usr/local
# Install dependencies with cache
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-default-groups --group server
# === Runtime base server image ===
FROM python:3.10-slim AS base_server
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends supervisor \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /fba /fba
COPY --from=builder /usr/local /usr/local
COPY deploy/backend/supervisord.conf /etc/supervisor/supervisord.conf
WORKDIR /fba/backend
# === FastAPI server image ===
FROM base_server AS fastapi_server
COPY deploy/backend/fba_server.conf /etc/supervisor/conf.d/
RUN mkdir -p /var/log/fastapi_server
EXPOSE 8001
CMD ["/usr/local/bin/granian", "main:app", "--interface", "asgi", "--host", "0.0.0.0", "--port","8000"]
# === Celery server image ===
FROM base_server AS celery
COPY deploy/backend/fba_celery.conf /etc/supervisor/conf.d/
RUN mkdir -p /var/log/celery
RUN chmod +x celery-start.sh
EXPOSE 8555
CMD ["./celery-start.sh"]
# Build image
FROM ${SERVER_TYPE}
+37 -202
View File
@@ -1,28 +1,37 @@
<div align="center">
<img alt="The logo includes the abstract combination of the three letters FBA, forming a lightning bolt that seems to spread out from the ground" width="320" src="https://wu-clan.github.io/picx-images-hosting/logo/fba.png">
# FastAPI Best Architecture
[![GitHub](https://img.shields.io/github/license/fastapi-practices/fastapi_best_architecture)](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE)
[![Static Badge](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
> [!TIP]
> You are viewing the pydantic-v1 branch, which is locked and no longer provides updates and fixes
Enterprise-level backend architecture solution
English | [简体中文](./README.zh-CN.md)
FastAPI framework based on the front-end and back-end separation of the middle and back-end solutions, follow
the [pseudo three-tier architecture](#pseudo-three-tier-architecture) design, support for **python3.10** and above
versions
[![GitHub](https://img.shields.io/github/license/fastapi-practices/fastapi_best_architecture)](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
![MySQL](https://img.shields.io/badge/MySQL-8.0%2B-%2300758f)
![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16.0%2B-%23336791)
![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy-2.0-%23778877)
[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://pydantic.dev)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
![Docker](https://img.shields.io/badge/Docker-%232496ED?logo=docker&logoColor=white)
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.com/invite/yNN3wTbVAC)
![Discord](https://img.shields.io/discord/1185035164577972344)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/fastapi-practices/fastapi_best_architecture)
Its purpose is to allow you to use it directly as the infrastructure of your new project, this repository as a template
library open to any person or enterprise can be used for free!
</div>
**Continuously updated and maintained**
## Pseudo 3-tier architecture
## Pseudo three-tier architecture
The mvc architecture is a common design pattern in python web, but the 3-tier architecture is even more fascinating
The mvc architecture is a common design pattern in python web, but the three-tier architecture is even more fascinating.
In python web development, there is no common standard for the concept of 3-tier architecture, so we'll call it a
pseudo 3-tier architecture here
In python web development, there is no common standard for the concept of three-tier architecture, so we'll call it a
pseudo three-tier architecture here
But please note that we don't have a traditional multi-app structure (django, springBoot...) If you don't like this
pattern, use templates to transform it to your heart's content!
| workflow | java | fastapi_best_architecture |
|----------------|----------------|---------------------------|
@@ -32,171 +41,16 @@ pseudo three-tier architecture here
| data access | dao / mapper | crud |
| model | model / entity | model |
## Online preview
## Help
Unfortunately, we don't have the funds to provide an online preview, you can deploy by checking
out [local-development](#local-development), or directly using [Docker](#docker-deploy)
to deploy, or at [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui)
See a preview of some of the screenshots
## Features
- [x] Design with FastAPI PEP 593 Annotated Parameters
- [x] Global asynchronous design with async/await + asgiref
- [x] Follows Restful API specification
- [x] Global SQLAlchemy 2.0 syntax
- [x] Casbin RBAC access control model
- [x] Celery asynchronous tasks
- [x] JWT middleware whitelist authentication
- [x] Global customizable time zone time
- [x] Docker / Docker-compose deployment
- [x] Pytest Unit Testing
## Built-in features
1. [x] User management: system user role management, permission assignment
2. [x] Department Management: Configure the system organization (company, department, group...)
3. [x] Menu Management: Configuration of system menus, user menus, button privilege identification
4. [x] Role Management: Assign role menu privileges, assign role routing privileges
5. [x] Dictionary Management: Maintain common fixed data or parameters within the system.
6. [x] Operation Logs: logging and querying of normal and abnormal system operations.
7. [x] Login Authentication: graphical authentication code background authentication login
8. [x] Login Logs: Logging and querying of normal and abnormal user logins
9. [x] Service Monitoring: server hardware device information and status
10. [x] Scheduled tasks: automated tasks, asynchronous tasks, and function invocation are supported
11. [x] Interface Documentation: Automatically generate online interactive API interface documentation.
## Local development
* Python: 3.10+
* Mysql: 8.0+
* Redis: The latest stable version is recommended
* Nodejs: 14.0+
### BackEnd
1. Install dependencies
```shell
pip install -r requirements.txt
```
2. Create a database `fba`, choose utf8mb4 encoding
3. Install and start Redis
4. Create a `.env` file in the `backend/app/` directory
```shell
cd backend/app/
touch .env
```
5. Copy `.env.example` to `.env`
```shell
cp .env.example .env
```
6. Database migration [alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
```shell
cd backend/app/
# Generate migration file
alembic revision --autogenerate
# Execute the migration
alembic upgrade head
```
7. Start celery worker and beat
```shell
celery -A tasks worker --loglevel=INFO
# Optional, if you don't need to use the scheduled task
celery -A tasks beat --loglevel=INFO
```
8. Modify the configuration file as needed
9. Execute the `backend/app/main.py` file to start the service
10. Browser access: http://127.0.0.1:8000/api/v1/docs
---
### Front
Go to [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui) for details
### Docker deploy
> [!WARNING]
> Default port conflict8000330663795672
>
> As a best practice, shut down on-premises services before deploymentmysqlredisrabbitmq...
1. Go to the directory where the ``docker-compose.yml`` file is located and create the environment variable
file ``.env``
```shell
cd deploy/docker-compose/
cp .env.server ../../backend/app/.env
# This command is optional
cp .env.docker .env
```
2. Modify the configuration file as needed
3. Execute the one-click boot command
```shell
docker-compose up -d --build
```
4. Wait for the command to complete automatically
5. Visit the browser: http://127.0.0.1:8000/api/v1/docs
## Test data
Initialize the test data using the `backend/sql/init_test_data.sql` file
## Development process
For reference only
### BackEnd
1. Define the database model (model) and remember to perform database migration for each change
2. Define the data validation model (schema)
3. Define routes (router) and views (api)
4. Define the business logic (service)
5. Write database operations (crud)
### Front
Go to [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui) for details
## Test
Execute unittests via pytest
1. Create the test database `fba_test`, select utf8mb4 encoding
2. Using `backend/sql/create_tables.sql` file to create database tables
3. Initialize the test data using the `backend/sql/init_pytest_data.sql` file
4. Enter the app directory
```shell
cd backend/app/
```
5. Execute the test command
```shell
pytest -vs --disable-warnings
```
For more details, please check
the [official documentation](https://fastapi-practices.github.io/fastapi_best_architecture_docs/)
## Contributors
<span style="margin: 0 5px;" ><a href="https://github.com/wu-clan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52145145?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/downdawn" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/41266749?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<a href="https://github.com/fastapi-practices/fastapi_best_architecture/graphs/contributors">
<img src="https://contrib.rocks/image?repo=fastapi-practices/fastapi_best_architecture"/>
</a>
## Special thanks
@@ -207,37 +61,18 @@ Execute unittests via pytest
- [Ruff](https://beta.ruff.rs/docs/)
- ...
## 互动
## Interactivity
有且仅有当前一个频道,请注意辨别真伪
<table>
<tr>
<td><a href="https://t.me/+ZlPhIFkPp7E4NGI1">直链跳转</a></td>
</tr>
<tr>
<td> Telegram </td>
</tr>
</table>
[Discord](https://wu-clan.github.io/homepage/)
## Sponsor us
If this program has helped you, you can sponsor us with some coffee beans :coffee:
<table>
<tr>
<td><img src="https://github.com/wu-clan/image/blob/master/pay/weixin.jpg?raw=true" width="180px" alt="WeChat"/>
<td><img src="https://github.com/wu-clan/image/blob/master/pay/zfb.jpg?raw=true" width="180px" alt="Alipay"/>
<td><img src="https://github.com/wu-clan/image/blob/master/pay/ERC20.jpg?raw=true" width="180px" alt="0x40D5e2304b452256afD9CE2d3d5531dc8d293138"/>
</tr>
<tr>
<td align="center">WeChat Pay</td>
<td align="center">Ali Pay</td>
<td align="center">ERC20</td>
</tr>
</table>
If this program has helped you, you can sponsor us with some coffee
beans: [:coffee: Sponsor :coffee:](https://wu-clan.github.io/sponsor/)
## License
This project is licensed under the terms of
This project is licensed by the terms of
the [MIT](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE) license
[![Stargazers over time](https://starchart.cc/fastapi-practices/fastapi_best_architecture.svg?variant=adaptive)](https://starchart.cc/fastapi-practices/fastapi_best_architecture)
+30 -194
View File
@@ -1,18 +1,27 @@
# FastAPI 最佳架构
<div align="center">
[![GitHub](https://img.shields.io/github/license/fastapi-practices/fastapi_best_architecture)](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE)
[![Static Badge](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
<img alt="Logo 包含了 FBA 三个字母抽象结合,形成了一个类似从地面扩散投影上来的闪电" width="320" src="https://wu-clan.github.io/picx-images-hosting/logo/fba.png">
> [!TIP]
> 你正在查看 pydantic-v1 分支,此分支已被锁定,不再提供更新和修复
# FastAPI Best Architecture
企业级后端架构解决方案
简体中文 | [English](./README.md)
基于 FastAPI 框架的前后端分离中后台解决方案,遵循[伪三层架构](#伪三层架构)设计, 支持 **python3.10** 及以上版本
[![GitHub](https://img.shields.io/github/license/fastapi-practices/fastapi_best_architecture)](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
![MySQL](https://img.shields.io/badge/MySQL-8.0%2B-%2300758f)
![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16.0%2B-%23336791)
![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy-2.0-%23778877)
[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://pydantic.dev)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
![Docker](https://img.shields.io/badge/Docker-%232496ED?logo=docker&logoColor=white)
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.com/invite/yNN3wTbVAC)
![Discord](https://img.shields.io/discord/1185035164577972344)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/fastapi-practices/fastapi_best_architecture)
它的目的是让你可以直接用它作为你新项目的基础架构,本仓库作为模板库公开,任何人或企业均可免费使用
**持续更新维护中**
</div>
## 伪三层架构
@@ -20,6 +29,8 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
在 python web 开发中,三层架构的概念并没有通用标准,所以这里我们称之为伪三层架构
但请注意,我们并没有传统的多应用程序结构(django、springBoot...),如果您不喜欢这种模式,可以使用模板对其进行随意改造!
| 工作流程 | java | fastapi_best_architecture |
|------|----------------|---------------------------|
| 视图 | controller | api |
@@ -28,170 +39,15 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
| 数据访问 | dao / mapper | crud |
| 模型 | model / entity | model |
## 在线预览
## 帮助
遗憾的是,我们没有资金提供在线预览,您可以通过查看[本地开发](#本地开发)进行部署,或直接使用 [Docker](#docker-部署)
进行部署,或者在 [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui)
查看部分截图预览
## 特征
- [x] 使用 FastAPI PEP 593 Annotated 参数设计
- [x] async/await + asgiref 的全局异步设计
- [x] 遵循 Restful API 规范
- [x] 全局 SQLAlchemy 2.0 语法
- [x] Casbin RBAC 访问控制模型
- [x] Celery 异步任务
- [x] JWT 中间件白名单认证
- [x] 全局自定义时区时间
- [x] Docker / Docker-compose 部署
- [x] Pytest 单元测试
## 内置功能
1. [x] 用户管理:系统用户角色管理,权限分配
2. [x] 部门管理:配置系统组织机构(公司、部门、小组...)
3. [x] 菜单管理:配置系统菜单,用户菜单,按钮权限标识
4. [x] 角色管理:角色菜单权限分配,角色路由权限分配
5. [x] 字典管理:维护系统内部常用固定数据或参数
6. [x] 操作日志:系统正常操作和异常操作日志记录和查询
7. [x] 登录认证:图形验证码后台认证登录
8. [x] 登录日志:用户正常登录和异常登录的日志记录与查询
9. [x] 服务监控:服务器硬件设备信息与状态
10. [x] 定时任务:自动化任务,异步任务,支持函数调用
11. [x] 接口文档:自动生成在线交互式 API 接口文档
## 本地开发
* Python 3.10+
* Mysql 8.0+
* Redis 推荐最新稳定版
* Nodejs 14.0+
### 后端
1. 安装依赖项
```shell
pip install -r requirements.txt
```
2. 创建一个数据库 `fba`,选择 utf8mb4 编码
3. 安装并启动 Redis
4. 在 `backend/app/` 目录下创建一个 `.env` 文件
```shell
cd backend/app/
touch .env
```
5. 复制 `.env.example` 到 `.env`
```shell
cp .env.example .env
```
6. 数据库迁移 [alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
```shell
cd backend/app/
# 生成迁移文件
alembic revision --autogenerate
# 执行迁移
alembic upgrade head
```
7. 启动 celery worker 和 beat
```shell
celery -A tasks worker --loglevel=INFO
# 可选,如果您不需要使用计划任务
celery -A tasks beat --loglevel=INFO
```
8. 按需修改配置文件
9. 执行 `backend/app/main.py` 文件启动服务
10. 浏览器访问:http://127.0.0.1:8000/api/v1/docs
---
### 前端
跳转 [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui) 查看详情
### Docker 部署
> [!WARNING]
> 默认端口冲突:8000330663795672
>
> 最佳做法是在部署之前关闭本地服务:mysqlredisrabbitmq...
1. 进入 `docker-compose.yml` 文件所在目录,创建环境变量文件`.env`
```shell
cd deploy/docker-compose/
cp .env.server ../../backend/app/.env
# 此命令为可选
cp .env.docker .env
```
2. 按需修改配置文件
3. 执行一键启动命令
```shell
docker-compose up -d --build
```
4. 等待命令自动完成
5. 浏览器访问:http://127.0.0.1:8000/api/v1/docs
## 测试数据
使用 `backend/sql/init_test_data.sql` 文件初始化测试数据
## 开发流程
仅供参考
### 后端:
1. 定义数据库模型(model),每次变化记得执行数据库迁移
2. 定义数据验证模型(schema)
3. 定义路由(router)和视图(api)
4. 定义业务逻辑(service
5. 编写数据库操作(crud
### 前端
跳转 [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui) 查看详情
## 测试
通过 pytest 执行单元测试
1. 创建测试数据库 `fba_test`,选择 utf8mb4 编码
2. 使用 `backend/sql/create_tables.sql` 文件创建数据库表
3. 使用 `backend/sql/init_pytest_data.sql` 文件初始化测试数据
4. 进入app目录
```shell
cd backend/app/
```
5. 执行测试命令
```shell
pytest -vs --disable-warnings
```
更多详情请查看 [官方文档](https://fastapi-practices.github.io/fastapi_best_architecture_docs/)
## 贡献者
<span style="margin: 0 5px;" ><a href="https://github.com/wu-clan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52145145?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/downdawn" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/41266749?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<a href="https://github.com/fastapi-practices/fastapi_best_architecture/graphs/contributors">
<img src="https://contrib.rocks/image?repo=fastapi-practices/fastapi_best_architecture"/>
</a>
## 特别鸣谢
@@ -204,34 +60,14 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
## 互动
有且仅有当前一个频道,请注意辨别真伪
<table>
<tr>
<td><a href="https://t.me/+ZlPhIFkPp7E4NGI1">直链跳转</a></td>
</tr>
<tr>
<td> Telegram </td>
</tr>
</table>
[Discord](https://wu-clan.github.io/homepage/)
## 赞助我们
如果此项目能够帮助到你,你可以赞助作者一些咖啡豆表示鼓励 :coffee:
<table>
<tr>
<td><img src="https://github.com/wu-clan/image/blob/master/pay/weixin.jpg?raw=true" width="180px" alt="Wechat"/>
<td><img src="https://github.com/wu-clan/image/blob/master/pay/zfb.jpg?raw=true" width="180px" alt="Alipay"/>
<td><img src="https://github.com/wu-clan/image/blob/master/pay/ERC20.jpg?raw=true" width="180px" alt="0x40D5e2304b452256afD9CE2d3d5531dc8d293138"/>
</tr>
<tr>
<td align="center">微信</td>
<td align="center">支付宝</td>
<td align="center">ERC20</td>
</tr>
</table>
如果此项目能够帮助到你,你可以赞助作者一些咖啡豆表示鼓励[:coffee: Sponsor :coffee:](https://wu-clan.github.io/sponsor/)
## 许可证
本项目根据 [MIT](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE) 许可证的条款进行许可
本项目 [MIT](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE) 许可证的条款进行许可
[![Stargazers over time](https://starchart.cc/fastapi-practices/fastapi_best_architecture.svg?variant=adaptive)](https://starchart.cc/fastapi-practices/fastapi_best_architecture)
-26
View File
@@ -1,26 +0,0 @@
FROM python:3.10-slim
WORKDIR /fba
COPY . .
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources \
&& sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list.d/debian.sources
RUN apt-get update \
&& apt-get install -y --no-install-recommends gcc python3-dev \
&& rm -rf /var/lib/apt/lists/*
# 某些包可能存在同步不及时导致安装失败的情况,可更改为官方源:https://pypi.org/simple
RUN pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple \
&& pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple
ENV TZ = Asia/Shanghai
RUN mkdir -p /var/log/fastapi_server
COPY ./deploy/fastapi_server.conf /etc/supervisor/conf.d/
EXPOSE 8001
CMD ["uvicorn", "backend.app.main:app", "--host", "127.0.0.1", "--port", "8000"]
+35
View File
@@ -0,0 +1,35 @@
# Env
ENVIRONMENT='dev'
# Database
DATABASE_TYPE='mysql'
DATABASE_HOST='127.0.0.1'
DATABASE_PORT=3306
DATABASE_USER='root'
DATABASE_PASSWORD='123456'
# Redis
REDIS_HOST='127.0.0.1'
REDIS_PORT=6379
REDIS_PASSWORD=''
REDIS_DATABASE=0
# Token
TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
# Opera Log
OPERA_LOG_ENCRYPT_SECRET_KEY='d77b25790a804c2b4a339dd0207941e4cefa5751935a33735bc73bb7071a005b'
# [ App ] task
# Celery
CELERY_BROKER_REDIS_DATABASE=1
# Rabbitmq
CELERY_RABBITMQ_HOST='127.0.0.1'
CELERY_RABBITMQ_PORT=5672
CELERY_RABBITMQ_USERNAME='guest'
CELERY_RABBITMQ_PASSWORD='guest'
# [ Plugin ] oauth2
OAUTH2_GITHUB_CLIENT_ID='test'
OAUTH2_GITHUB_CLIENT_SECRET='test'
OAUTH2_GOOGLE_CLIENT_ID='test'
OAUTH2_GOOGLE_CLIENT_SECRET='test'
OAUTH2_LINUX_DO_CLIENT_ID='test'
OAUTH2_LINUX_DO_CLIENT_SECRET='test'
# [ Plugin ] email
EMAIL_USERNAME=''
EMAIL_PASSWORD=''
+6
View File
@@ -0,0 +1,6 @@
__pycache__/
.env
alembic/versions/
static/media/
*.log
celerybeat-schedule.*
+83
View File
@@ -0,0 +1,83 @@
# FastAPI Best Architecture - Backend
## Docker
1. Make sure you're at the root of the project
2. Run the following Docker command to build container:
```shell
docker build -f Dockerfile -t fba_backend_independent .
```
3. Start container
Native boot needs to change `127.0.0.1` in `.env` to `host.docker.internal`
```shell
docker run -d -p 8000:8000 --name fba_server fba_backend_independent
```
## Contributing
1. Prerequisites
- Python >= 3.10
- Git
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
- Fork this repository to your GitHub account
2. Installation and setup
Clone your forked repository:
```shell
git clone https://github.com/<your account>/fastapi_best_architecture.git
```
Go to the root directory of the project, open the terminal, and run the following command:
```sh
uv sync --frozen
```
3. Checkout
Checkout a new branch and make your changes
```shell
git checkout -b your-new-feature-branch
```
4. Format and Lint
Auto-formatting and lint via `pre-commit`
```shell
pre-commit run --all-files
```
5. Commit and push
Commit your changes and push your branch to the GitHub.
6. PR
Create a PR via GitHub
## Scripts
> [!WARNING]
>
> The following script may not apply to the Windows platform
>
> It is recommended to execute under the backend directory, and chmod authorization may be required
- `pre_start.sh`: Perform automatic database migration
- `celery-start.sh`: For celery docker script, implementation is not recommended
- `scripts/format.sh`: Perform ruff format check
- `scripts/lint.sh`: Perform pre-commit formatting
- `scripts/export.sh`: Execute uv export dependency package
+7
View File
@@ -0,0 +1,7 @@
from backend.common.i18n import i18n
__version__ = '1.8.2'
# 初始化 i18n
i18n.load_locales()
+26 -11
View File
@@ -1,11 +1,13 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
# path to migration scripts.
# Use forward slashes (/) also on windows to provide an os agnostic path
script_location = alembic
# template used to generate migration files
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(hour).2d-%%(minute).2d_%%(rev)s_%%(slug)s
# 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.
@@ -13,14 +15,13 @@ prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python-dateutil library that can be
# installed by adding `alembic[tz]` to the pip requirements
# string value is passed to dateutil.tz.gettz()
# 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
# max length of characters to apply to the "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
@@ -35,16 +36,24 @@ prepend_sys_path = .
# 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"
# 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. Valid values are:
# 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 = os # default: use os.pathsep
# 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
@@ -64,6 +73,12 @@ sqlalchemy.url = driver://user:pass@localhost/dbname
# 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
+3
View File
@@ -0,0 +1,3 @@
Generic single-database configuration with an async dbapi.
If you add a new app to the framework, be sure to read the contents of the `env.py` file.
+122
View File
@@ -0,0 +1,122 @@
import asyncio
import os
from logging.config import fileConfig
from alembic import context
from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config
from backend.app import get_app_models
from backend.common.model import MappedBase
from backend.core import path_conf
from backend.database.db import SQLALCHEMY_DATABASE_URL
from backend.plugin.tools import get_plugin_models
# import models
for cls in get_app_models() + get_plugin_models():
class_name = cls.__name__
if class_name not in globals():
globals()[class_name] = cls
if not os.path.exists(path_conf.ALEMBIC_VERSION_DIR):
os.makedirs(path_conf.ALEMBIC_VERSION_DIR)
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
alembic_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)
# model's MetaData object
# for 'autogenerate' support
target_metadata = MappedBase.metadata
# other values from the config, defined by the needs of env.py,
alembic_config.set_main_option(
'sqlalchemy.url',
SQLALCHEMY_DATABASE_URL.render_as_string(hide_password=False).replace('%', '%%'),
)
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = alembic_config.get_main_option('sqlalchemy.url')
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={'paramstyle': 'named'},
compare_type=True,
compare_server_default=True,
transaction_per_migration=True,
)
with context.begin_transaction():
context.run_migrations()
def do_run_migrations(connection: Connection) -> None:
# 当迁移无变化时,不生成迁移记录
def process_revision_directives(context, revision, directives) -> None: # noqa: ANN001
if alembic_config.cmd_opts.autogenerate:
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
print('\nNo changes in model detected')
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
compare_server_default=True,
transaction_per_migration=True,
process_revision_directives=process_revision_directives,
)
with context.begin_transaction():
context.run_migrations()
async def run_async_migrations() -> None:
"""In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = async_engine_from_config(
alembic_config.get_section(alembic_config.config_ini_section, {}),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
await connectable.dispose()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode."""
asyncio.run(run_async_migrations())
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
@@ -7,6 +7,7 @@ Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
import backend.common.model
${imports if imports else ""}
# revision identifiers, used by Alembic.
-27
View File
@@ -1,27 +0,0 @@
# Env: dev、pro
ENVIRONMENT='dev'
# MySQL
DB_HOST='127.0.0.1'
DB_PORT=3306
DB_USER='root'
DB_PASSWORD='123456'
# Redis
REDIS_HOST='127.0.0.1'
REDIS_PORT=6379
REDIS_PASSWORD=''
REDIS_DATABASE=0
# Celery
CELERY_REDIS_HOST='127.0.0.1'
CELERY_REDIS_PORT=6379
CELERY_REDIS_PASSWORD=''
CELERY_BROKER_REDIS_DATABASE=1
CELERY_BACKEND_REDIS_DATABASE=2
# Rabbitmq
RABBITMQ_HOST='127.0.0.1'
RABBITMQ_PORT=5672
RABBITMQ_USERNAME='guest'
RABBITMQ_PASSWORD='guest'
# Token
TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
# Opera Log
OPERA_LOG_ENCRYPT_SECRET_KEY='d77b25790a804c2b4a339dd0207941e4cefa5751935a33735bc73bb7071a005b'
+29 -2
View File
@@ -1,2 +1,29 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os.path
from backend.core.path_conf import BASE_PATH
from backend.utils.import_parse import get_model_objects
def get_app_models() -> list[type]:
"""获取 app 所有模型类"""
app_path = BASE_PATH / 'app'
list_dirs = os.listdir(app_path)
apps = [d for d in list_dirs if os.path.isdir(os.path.join(app_path, d)) and d != '__pycache__']
objs = []
for app in apps:
module_path = f'backend.app.{app}.model'
obj = get_model_objects(module_path)
if obj:
objs.extend(obj)
return objs
# import all app models for auto create db tables
for cls in get_app_models():
class_name = cls.__name__
if class_name not in globals():
globals()[class_name] = cls
View File
View File
+14
View File
@@ -0,0 +1,14 @@
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.monitor import router as monitor_router
from backend.app.admin.api.v1.sys import router as sys_router
from backend.core.conf import settings
v1 = APIRouter(prefix=settings.FASTAPI_API_V1_PATH)
v1.include_router(auth_router)
v1.include_router(sys_router)
v1.include_router(log_router)
v1.include_router(monitor_router)
@@ -0,0 +1,9 @@
from fastapi import APIRouter
from backend.app.admin.api.v1.auth.auth import router as auth_router
from backend.app.admin.api.v1.auth.captcha import router as captcha_router
router = APIRouter(prefix='/auth')
router.include_router(auth_router, tags=['授权'])
router.include_router(captcha_router, tags=['验证码'])
+57
View File
@@ -0,0 +1,57 @@
from typing import Annotated
from fastapi import APIRouter, Depends, Request, Response
from fastapi.security import HTTPBasicCredentials
from fastapi_limiter.depends import RateLimiter
from starlette.background import BackgroundTasks
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.common.security.jwt import DependsJwtAuth
from backend.database.db import CurrentSession, CurrentSessionTransaction
router = APIRouter()
@router.post('/login/swagger', summary='swagger 调试专用', description='用于快捷获取 token 进行 swagger 认证')
async def login_swagger(
db: CurrentSessionTransaction, obj: Annotated[HTTPBasicCredentials, Depends()]
) -> GetSwaggerToken:
token, user = await auth_service.swagger_login(db=db, obj=obj)
return GetSwaggerToken(access_token=token, user=user)
@router.post(
'/login',
summary='用户登录',
description='json 格式登录, 仅支持在第三方api工具调试, 例如: postman',
dependencies=[Depends(RateLimiter(times=5, minutes=1))],
)
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)
return response_base.success(data=data)
@router.get('/codes', summary='获取所有授权码', description='适配 vben admin v5', dependencies=[DependsJwtAuth])
async def get_codes(db: CurrentSession, request: Request) -> ResponseSchemaModel[list[str]]:
codes = await auth_service.get_codes(db=db, request=request)
return response_base.success(data=codes)
@router.post('/refresh', summary='刷新 token')
async def refresh_token(db: CurrentSession, request: Request) -> ResponseSchemaModel[GetNewToken]:
data = await auth_service.refresh_token(db=db, request=request)
return response_base.success(data=data)
@router.post('/logout', summary='用户登出')
async def logout(request: Request, response: Response) -> ResponseModel:
await auth_service.logout(request=request, response=response)
return response_base.success()
+34
View File
@@ -0,0 +1,34 @@
from uuid import uuid4
from fast_captcha import img_captcha
from fastapi import APIRouter, Depends
from fastapi_limiter.depends import RateLimiter
from starlette.concurrency import run_in_threadpool
from backend.app.admin.schema.captcha import GetCaptchaDetail
from backend.common.response.response_schema import ResponseSchemaModel, response_base
from backend.core.conf import settings
from backend.database.redis import redis_client
router = APIRouter()
@router.get(
'/captcha',
summary='获取登录验证码',
dependencies=[Depends(RateLimiter(times=5, seconds=10))],
)
async def get_captcha() -> ResponseSchemaModel[GetCaptchaDetail]:
"""
此接口可能存在性能损耗,尽管是异步接口,但是验证码生成是IO密集型任务,使用线程池尽量减少性能损耗
"""
img_type: str = 'base64'
img, code = await run_in_threadpool(img_captcha, img_byte=img_type)
uuid = str(uuid4())
await redis_client.set(
f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{uuid}',
code,
ex=settings.CAPTCHA_LOGIN_EXPIRE_SECONDS,
)
data = GetCaptchaDetail(uuid=uuid, img_type=img_type, image=img)
return response_base.success(data=data)
+9
View File
@@ -0,0 +1,9 @@
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=['操作日志'])
+61
View File
@@ -0,0 +1,61 @@
from typing import Annotated
from fastapi import APIRouter, Depends, 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.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
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=[
Depends(RequestPermission('log:login:del')),
DependsRBAC,
],
)
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=[
Depends(RequestPermission('log:login:clear')),
DependsRBAC,
],
)
async def delete_all_login_logs(db: CurrentSessionTransaction) -> ResponseModel:
await login_log_service.delete_all(db=db)
return response_base.success()
+61
View File
@@ -0,0 +1,61 @@
from typing import Annotated
from fastapi import APIRouter, Depends, 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.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
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=[
Depends(RequestPermission('log:opera:del')),
DependsRBAC,
],
)
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=[
Depends(RequestPermission('log:opera:clear')),
DependsRBAC,
],
)
async def delete_all_opera_logs(db: CurrentSessionTransaction) -> ResponseModel:
await opera_log_service.delete_all(db=db)
return response_base.success()
@@ -0,0 +1,11 @@
from fastapi import APIRouter
from backend.app.admin.api.v1.monitor.online import router as token_router
from backend.app.admin.api.v1.monitor.redis import router as redis_router
from backend.app.admin.api.v1.monitor.server import router as server_router
router = APIRouter(prefix='/monitors')
router.include_router(redis_router, prefix='/redis', tags=['redis监控'])
router.include_router(server_router, prefix='/server', tags=['服务器监控'])
router.include_router(token_router, prefix='/sessions', tags=['会话监控'])
@@ -0,0 +1,83 @@
import json
from typing import Annotated
from fastapi import APIRouter, Path, Query
from backend.app.admin.schema.token import GetTokenDetail
from backend.common.enums import StatusType
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
from backend.common.security.jwt import DependsJwtAuth, DependsSuperUser, jwt_decode, revoke_token
from backend.core.conf import settings
from backend.database.redis import redis_client
router = APIRouter()
@router.get('', summary='获取在线用户', dependencies=[DependsJwtAuth])
async def get_sessions(
username: Annotated[str | None, Query(description='用户名')] = None,
) -> ResponseSchemaModel[list[GetTokenDetail]]:
token_keys = await redis_client.keys(f'{settings.TOKEN_REDIS_PREFIX}:*')
online_clients = await redis_client.smembers(settings.TOKEN_ONLINE_REDIS_PREFIX)
data: list[GetTokenDetail] = []
def append_token_detail() -> None:
data.append(
token_detail.model_copy(
update={
'username': extra_info.get('username', '未知'),
'nickname': extra_info.get('nickname', '未知'),
'ip': extra_info.get('ip', '未知'),
'os': extra_info.get('os', '未知'),
'browser': extra_info.get('browser', '未知'),
'device': extra_info.get('device', '未知'),
'last_login_time': extra_info.get('last_login_time', '未知'),
},
),
)
for key in token_keys:
token = await redis_client.get(key)
token_payload = jwt_decode(token)
user_id = token_payload.id
session_uuid = token_payload.session_uuid
token_detail = GetTokenDetail(
id=user_id,
session_uuid=session_uuid,
username='未知',
nickname='未知',
ip='未知',
os='未知',
browser='未知',
device='未知',
status=StatusType.enable if session_uuid in online_clients else StatusType.disable,
last_login_time='未知',
expire_time=token_payload.expire_time,
)
extra_info = await redis_client.get(f'{settings.TOKEN_EXTRA_INFO_REDIS_PREFIX}:{user_id}:{session_uuid}')
if extra_info:
extra_info = json.loads(extra_info)
# 排除 swagger 登录生成的 token
if extra_info.get('swagger') is None:
if username is not None:
if username == extra_info.get('username'):
append_token_detail()
else:
append_token_detail()
else:
data.append(token_detail)
return response_base.success(data=data)
@router.delete(
'/{pk}',
summary='强制下线',
dependencies=[DependsSuperUser],
)
async def delete_session(
pk: Annotated[int, Path(description='用户 ID')],
session_uuid: Annotated[str, Query(description='会话 UUID')],
) -> ResponseModel:
await revoke_token(pk, session_uuid)
return response_base.success()
+16
View File
@@ -0,0 +1,16 @@
from fastapi import APIRouter
from backend.common.response.response_schema import ResponseModel, response_base
from backend.common.security.jwt import DependsJwtAuth
from backend.utils.redis_info import redis_info
router = APIRouter()
@router.get('', summary='redis 监控', dependencies=[DependsJwtAuth])
async def get_redis_info() -> ResponseModel:
data = {
'info': await redis_info.get_info(),
'stats': await redis_info.get_stats(),
}
return response_base.success(data=data)
@@ -1,23 +1,21 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from starlette.concurrency import run_in_threadpool
from backend.app.common.jwt import DependsJwtAuth
from backend.app.common.response.response_schema import response_base
from backend.app.utils.server_info import server_info
from backend.common.response.response_schema import ResponseModel, response_base
from backend.common.security.jwt import DependsJwtAuth
from backend.utils.server_info import server_info
router = APIRouter()
@router.get('/server', summary='server 监控', dependencies=[DependsJwtAuth])
async def get_server_info():
"""IO密集型任务,使用线程池尽量减少性能损耗"""
@router.get('', summary='server 监控', dependencies=[DependsJwtAuth])
async def get_server_info() -> ResponseModel:
data = {
# 扔到线程池,避免阻塞
'cpu': await run_in_threadpool(server_info.get_cpu_info),
'mem': await run_in_threadpool(server_info.get_mem_info),
'sys': await run_in_threadpool(server_info.get_sys_info),
'disk': await run_in_threadpool(server_info.get_disk_info),
'service': await run_in_threadpool(server_info.get_service_info),
}
return await response_base.success(data=data)
return response_base.success(data=data)
+21
View File
@@ -0,0 +1,21 @@
from fastapi import APIRouter
from backend.app.admin.api.v1.sys.data_rule import router as data_rule_router
from backend.app.admin.api.v1.sys.data_scope import router as data_scope_router
from backend.app.admin.api.v1.sys.dept import router as dept_router
from backend.app.admin.api.v1.sys.files import router as file_router
from backend.app.admin.api.v1.sys.menu import router as menu_router
from backend.app.admin.api.v1.sys.plugin import router as plugin_router
from backend.app.admin.api.v1.sys.role import router as role_router
from backend.app.admin.api.v1.sys.user import router as user_router
router = APIRouter(prefix='/sys')
router.include_router(dept_router, prefix='/depts', tags=['系统部门'])
router.include_router(menu_router, prefix='/menus', tags=['系统菜单'])
router.include_router(role_router, prefix='/roles', tags=['系统角色'])
router.include_router(user_router, prefix='/users', tags=['系统用户'])
router.include_router(data_rule_router, prefix='/data-rules', tags=['系统数据规则'])
router.include_router(data_scope_router, prefix='/data-scopes', tags=['系统数据范围'])
router.include_router(file_router, prefix='/files', tags=['系统文件'])
router.include_router(plugin_router, prefix='/plugins', tags=['系统插件'])
+112
View File
@@ -0,0 +1,112 @@
from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query
from backend.app.admin.schema.data_rule import (
CreateDataRuleParam,
DeleteDataRuleParam,
GetDataRuleColumnDetail,
GetDataRuleDetail,
UpdateDataRuleParam,
)
from backend.app.admin.service.data_rule_service import data_rule_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.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.database.db import CurrentSession, CurrentSessionTransaction
router = APIRouter()
@router.get('/models', summary='获取数据规则可用模型', dependencies=[DependsJwtAuth])
async def get_data_rule_models() -> ResponseSchemaModel[list[str]]:
models = await data_rule_service.get_models()
return response_base.success(data=models)
@router.get('/models/{model}/columns', summary='获取数据规则可用模型列', dependencies=[DependsJwtAuth])
async def get_data_rule_model_columns(
model: Annotated[str, Path(description='模型名称')],
) -> ResponseSchemaModel[list[GetDataRuleColumnDetail]]:
models = await data_rule_service.get_columns(model=model)
return response_base.success(data=models)
@router.get('/all', summary='获取所有数据规则', dependencies=[DependsJwtAuth])
async def get_all_data_rules(db: CurrentSession) -> ResponseSchemaModel[list[GetDataRuleDetail]]:
data = await data_rule_service.get_all(db=db)
return response_base.success(data=data)
@router.get('/{pk}', summary='获取数据规则详情', dependencies=[DependsJwtAuth])
async def get_data_rule(
db: CurrentSession,
pk: Annotated[int, Path(description='数据规则 ID')],
) -> ResponseSchemaModel[GetDataRuleDetail]:
data = await data_rule_service.get(db=db, pk=pk)
return response_base.success(data=data)
@router.get(
'',
summary='分页获取所有数据规则',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_data_rules_paginated(
db: CurrentSession,
name: Annotated[str | None, Query(description='规则名称')] = None,
) -> ResponseSchemaModel[PageData[GetDataRuleDetail]]:
page_data = await data_rule_service.get_list(db=db, name=name)
return response_base.success(data=page_data)
@router.post(
'',
summary='创建数据规则',
dependencies=[
Depends(RequestPermission('data:rule:add')),
DependsRBAC,
],
)
async def create_data_rule(db: CurrentSessionTransaction, obj: CreateDataRuleParam) -> ResponseModel:
await data_rule_service.create(db=db, obj=obj)
return response_base.success()
@router.put(
'/{pk}',
summary='更新数据规则',
dependencies=[
Depends(RequestPermission('data:rule:edit')),
DependsRBAC,
],
)
async def update_data_rule(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='数据规则 ID')],
obj: UpdateDataRuleParam,
) -> ResponseModel:
count = await data_rule_service.update(db=db, pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.delete(
'',
summary='批量删除数据规则',
dependencies=[
Depends(RequestPermission('data:rule:del')),
DependsRBAC,
],
)
async def delete_data_rules(db: CurrentSessionTransaction, obj: DeleteDataRuleParam) -> ResponseModel:
count = await data_rule_service.delete(db=db, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
+128
View File
@@ -0,0 +1,128 @@
from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query
from backend.app.admin.schema.data_scope import (
CreateDataScopeParam,
DeleteDataScopeParam,
GetDataScopeDetail,
GetDataScopeWithRelationDetail,
UpdateDataScopeParam,
UpdateDataScopeRuleParam,
)
from backend.app.admin.service.data_scope_service import data_scope_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.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.database.db import CurrentSession, CurrentSessionTransaction
router = APIRouter()
@router.get('/all', summary='获取所有数据范围', dependencies=[DependsJwtAuth])
async def get_all_data_scope(db: CurrentSession) -> ResponseSchemaModel[list[GetDataScopeDetail]]:
data = await data_scope_service.get_all(db=db)
return response_base.success(data=data)
@router.get('/{pk}', summary='获取数据范围详情', dependencies=[DependsJwtAuth])
async def get_data_scope(
db: CurrentSession,
pk: Annotated[int, Path(description='数据范围 ID')],
) -> ResponseSchemaModel[GetDataScopeDetail]:
data = await data_scope_service.get(db=db, pk=pk)
return response_base.success(data=data)
@router.get('/{pk}/rules', summary='获取数据范围所有规则', dependencies=[DependsJwtAuth])
async def get_data_scope_rules(
db: CurrentSession,
pk: Annotated[int, Path(description='数据范围 ID')],
) -> ResponseSchemaModel[GetDataScopeWithRelationDetail]:
data = await data_scope_service.get_rules(db=db, pk=pk)
return response_base.success(data=data)
@router.get(
'',
summary='分页获取所有数据范围',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_data_scopes_paginated(
db: CurrentSession,
name: Annotated[str | None, Query(description='范围名称')] = None,
status: Annotated[int | None, Query(description='状态')] = None,
) -> ResponseSchemaModel[PageData[GetDataScopeDetail]]:
page_data = await data_scope_service.get_list(db=db, name=name, status=status)
return response_base.success(data=page_data)
@router.post(
'',
summary='创建数据范围',
dependencies=[
Depends(RequestPermission('data:scope:add')),
DependsRBAC,
],
)
async def create_data_scope(db: CurrentSessionTransaction, obj: CreateDataScopeParam) -> ResponseModel:
await data_scope_service.create(db=db, obj=obj)
return response_base.success()
@router.put(
'/{pk}',
summary='更新数据范围',
dependencies=[
Depends(RequestPermission('data:scope:edit')),
DependsRBAC,
],
)
async def update_data_scope(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='数据范围 ID')],
obj: UpdateDataScopeParam,
) -> ResponseModel:
count = await data_scope_service.update(db=db, pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put(
'/{pk}/rules',
summary='更新数据范围规则',
dependencies=[
Depends(RequestPermission('data:scope:rule:edit')),
DependsRBAC,
],
)
async def update_data_scope_rules(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='数据范围 ID')],
rule_ids: UpdateDataScopeRuleParam,
) -> ResponseModel:
count = await data_scope_service.update_data_scope_rule(db=db, pk=pk, rule_ids=rule_ids)
if count > 0:
return response_base.success()
return response_base.fail()
@router.delete(
'',
summary='批量删除数据范围',
dependencies=[
Depends(RequestPermission('data:scope:del')),
DependsRBAC,
],
)
async def delete_data_scopes(db: CurrentSessionTransaction, obj: DeleteDataScopeParam) -> ResponseModel:
count = await data_scope_service.delete(db=db, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
+79
View File
@@ -0,0 +1,79 @@
from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query, Request
from backend.app.admin.schema.dept import CreateDeptParam, GetDeptDetail, GetDeptTree, UpdateDeptParam
from backend.app.admin.service.dept_service import dept_service
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
from backend.common.security.jwt import DependsJwtAuth
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.database.db import CurrentSession, CurrentSessionTransaction
router = APIRouter()
@router.get('/{pk}', summary='获取部门详情', dependencies=[DependsJwtAuth])
async def get_dept(
db: CurrentSession, pk: Annotated[int, Path(description='部门 ID')]
) -> ResponseSchemaModel[GetDeptDetail]:
data = await dept_service.get(db=db, pk=pk)
return response_base.success(data=data)
@router.get('', summary='获取部门树', dependencies=[DependsJwtAuth])
async def get_dept_tree(
db: CurrentSession,
request: Request,
name: Annotated[str | None, Query(description='部门名称')] = None,
leader: Annotated[str | None, Query(description='部门负责人')] = None,
phone: Annotated[str | None, Query(description='联系电话')] = None,
status: Annotated[int | None, Query(description='状态')] = None,
) -> ResponseSchemaModel[list[GetDeptTree]]:
dept = await dept_service.get_tree(db=db, request=request, name=name, leader=leader, phone=phone, status=status)
return response_base.success(data=dept)
@router.post(
'',
summary='创建部门',
dependencies=[
Depends(RequestPermission('sys:dept:add')),
DependsRBAC,
],
)
async def create_dept(db: CurrentSessionTransaction, obj: CreateDeptParam) -> ResponseModel:
await dept_service.create(db=db, obj=obj)
return response_base.success()
@router.put(
'/{pk}',
summary='更新部门',
dependencies=[
Depends(RequestPermission('sys:dept:edit')),
DependsRBAC,
],
)
async def update_dept(
db: CurrentSessionTransaction, pk: Annotated[int, Path(description='部门 ID')], obj: UpdateDeptParam
) -> ResponseModel:
count = await dept_service.update(db=db, pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.delete(
'/{pk}',
summary='删除部门',
dependencies=[
Depends(RequestPermission('sys:dept:del')),
DependsRBAC,
],
)
async def delete_dept(db: CurrentSessionTransaction, pk: Annotated[int, Path(description='部门 ID')]) -> ResponseModel:
count = await dept_service.delete(db=db, pk=pk)
if count > 0:
return response_base.success()
return response_base.fail()
+25
View File
@@ -0,0 +1,25 @@
from typing import Annotated
from fastapi import APIRouter, Depends, File, UploadFile
from backend.common.dataclasses import UploadUrl
from backend.common.response.response_schema import ResponseSchemaModel, response_base
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.utils.file_ops import upload_file, upload_file_verify
router = APIRouter()
@router.post(
'/upload',
summary='文件上传',
dependencies=[
Depends(RequestPermission('sys:file:upload')),
DependsRBAC,
],
)
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}'})
+82
View File
@@ -0,0 +1,82 @@
from typing import Annotated, Any
from fastapi import APIRouter, Depends, Path, Query, Request
from backend.app.admin.schema.menu import CreateMenuParam, GetMenuDetail, GetMenuTree, UpdateMenuParam
from backend.app.admin.service.menu_service import menu_service
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
from backend.common.security.jwt import DependsJwtAuth
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.database.db import CurrentSession, CurrentSessionTransaction
router = APIRouter()
@router.get('/sidebar', summary='获取用户菜单侧边栏', description='已适配 vben admin v5', dependencies=[DependsJwtAuth])
async def get_user_sidebar(db: CurrentSession, request: Request) -> ResponseSchemaModel[list[dict[str, Any] | None]]:
menu = await menu_service.get_sidebar(db=db, request=request)
return response_base.success(data=menu)
@router.get('/{pk}', summary='获取菜单详情', dependencies=[DependsJwtAuth])
async def get_menu(
db: CurrentSession, pk: Annotated[int, Path(description='菜单 ID')]
) -> ResponseSchemaModel[GetMenuDetail]:
data = await menu_service.get(db=db, pk=pk)
return response_base.success(data=data)
@router.get('', summary='获取菜单树', dependencies=[DependsJwtAuth])
async def get_menu_tree(
db: CurrentSession,
title: Annotated[str | None, Query(description='菜单标题')] = None,
status: Annotated[int | None, Query(description='状体')] = None,
) -> ResponseSchemaModel[list[GetMenuTree]]:
menu = await menu_service.get_tree(db=db, title=title, status=status)
return response_base.success(data=menu)
@router.post(
'',
summary='创建菜单',
dependencies=[
Depends(RequestPermission('sys:menu:add')),
DependsRBAC,
],
)
async def create_menu(db: CurrentSessionTransaction, obj: CreateMenuParam) -> ResponseModel:
await menu_service.create(db=db, obj=obj)
return response_base.success()
@router.put(
'/{pk}',
summary='更新菜单',
dependencies=[
Depends(RequestPermission('sys:menu:edit')),
DependsRBAC,
],
)
async def update_menu(
db: CurrentSessionTransaction, pk: Annotated[int, Path(description='菜单 ID')], obj: UpdateMenuParam
) -> ResponseModel:
count = await menu_service.update(db=db, pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.delete(
'/{pk}',
summary='删除菜单',
dependencies=[
Depends(RequestPermission('sys:menu:del')),
DependsRBAC,
],
)
async def delete_menu(db: CurrentSessionTransaction, pk: Annotated[int, Path(description='菜单 ID')]) -> ResponseModel:
count = await menu_service.delete(db=db, pk=pk)
if count > 0:
return response_base.success()
return response_base.fail()
+89
View File
@@ -0,0 +1,89 @@
from typing import Annotated, Any
from fastapi import APIRouter, Depends, 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
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
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=[
Depends(RequestPermission('sys:plugin:install')),
DependsRBAC,
],
)
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=[
Depends(RequestPermission('sys:plugin:uninstall')),
DependsRBAC,
],
)
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=[
Depends(RequestPermission('sys:plugin:edit')),
DependsRBAC,
],
)
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'},
)
+154
View File
@@ -0,0 +1,154 @@
from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query
from backend.app.admin.schema.menu import GetMenuTree
from backend.app.admin.schema.role import (
CreateRoleParam,
DeleteRoleParam,
GetRoleDetail,
GetRoleWithRelationDetail,
UpdateRoleMenuParam,
UpdateRoleParam,
UpdateRoleScopeParam,
)
from backend.app.admin.service.role_service import role_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.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.database.db import CurrentSession, CurrentSessionTransaction
router = APIRouter()
@router.get('/all', summary='获取所有角色', dependencies=[DependsJwtAuth])
async def get_all_roles(db: CurrentSession) -> ResponseSchemaModel[list[GetRoleDetail]]:
data = await role_service.get_all(db=db)
return response_base.success(data=data)
@router.get('/{pk}/menus', summary='获取角色菜单树', dependencies=[DependsJwtAuth])
async def get_role_menu_tree(
db: CurrentSession,
pk: Annotated[int, Path(description='角色 ID')],
) -> ResponseSchemaModel[list[GetMenuTree] | None]:
menu = await role_service.get_menu_tree(db=db, pk=pk)
return response_base.success(data=menu)
@router.get('/{pk}/scopes', summary='获取角色所有数据范围', dependencies=[DependsJwtAuth])
async def get_role_scopes(
db: CurrentSession, pk: Annotated[int, Path(description='角色 ID')]
) -> ResponseSchemaModel[list[int]]:
rule = await role_service.get_scopes(db=db, pk=pk)
return response_base.success(data=rule)
@router.get('/{pk}', summary='获取角色详情', dependencies=[DependsJwtAuth])
async def get_role(
db: CurrentSession, pk: Annotated[int, Path(description='角色 ID')]
) -> ResponseSchemaModel[GetRoleWithRelationDetail]:
data = await role_service.get(db=db, pk=pk)
return response_base.success(data=data)
@router.get(
'',
summary='分页获取所有角色',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_roles_paginated(
db: CurrentSession,
name: Annotated[str | None, Query(description='角色名称')] = None,
status: Annotated[int | None, Query(description='状态')] = None,
) -> ResponseSchemaModel[PageData[GetRoleDetail]]:
page_data = await role_service.get_list(db=db, name=name, status=status)
return response_base.success(data=page_data)
@router.post(
'',
summary='创建角色',
dependencies=[
Depends(RequestPermission('sys:role:add')),
DependsRBAC,
],
)
async def create_role(db: CurrentSessionTransaction, obj: CreateRoleParam) -> ResponseModel:
await role_service.create(db=db, obj=obj)
return response_base.success()
@router.put(
'/{pk}',
summary='更新角色',
dependencies=[
Depends(RequestPermission('sys:role:edit')),
DependsRBAC,
],
)
async def update_role(
db: CurrentSessionTransaction, pk: Annotated[int, Path(description='角色 ID')], obj: UpdateRoleParam
) -> ResponseModel:
count = await role_service.update(db=db, pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put(
'/{pk}/menus',
summary='更新角色菜单',
dependencies=[
Depends(RequestPermission('sys:role:menu:edit')),
DependsRBAC,
],
)
async def update_role_menus(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='角色 ID')],
menu_ids: UpdateRoleMenuParam,
) -> ResponseModel:
count = await role_service.update_role_menu(db=db, pk=pk, menu_ids=menu_ids)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put(
'/{pk}/scopes',
summary='更新角色数据范围',
dependencies=[
Depends(RequestPermission('sys:role:scope:edit')),
DependsRBAC,
],
)
async def update_role_scopes(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='角色 ID')],
scope_ids: UpdateRoleScopeParam,
) -> ResponseModel:
count = await role_service.update_role_scope(db=db, pk=pk, scope_ids=scope_ids)
if count > 0:
return response_base.success()
return response_base.fail()
@router.delete(
'',
summary='批量删除角色',
dependencies=[
Depends(RequestPermission('sys:role:del')),
DependsRBAC,
],
)
async def delete_roles(db: CurrentSessionTransaction, obj: DeleteRoleParam) -> ResponseModel:
count = await role_service.delete(db=db, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
+172
View File
@@ -0,0 +1,172 @@
from typing import Annotated
from fastapi import APIRouter, Body, Depends, Path, Query, Request
from backend.app.admin.schema.role import GetRoleDetail
from backend.app.admin.schema.user import (
AddUserParam,
GetCurrentUserInfoWithRelationDetail,
GetUserInfoWithRelationDetail,
ResetPasswordParam,
UpdateUserParam,
)
from backend.app.admin.service.user_service import user_service
from backend.common.enums import UserPermissionType
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, DependsSuperUser
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
from backend.database.db import CurrentSession, CurrentSessionTransaction
router = APIRouter()
@router.get('/me', summary='获取当前用户信息', dependencies=[DependsJwtAuth])
async def get_current_user(request: Request) -> ResponseSchemaModel[GetCurrentUserInfoWithRelationDetail]:
data = request.user.model_dump()
return response_base.success(data=data)
@router.get('/{pk}', summary='获取用户信息', dependencies=[DependsJwtAuth])
async def get_userinfo(
db: CurrentSession,
pk: Annotated[int, Path(description='用户 ID')],
) -> ResponseSchemaModel[GetUserInfoWithRelationDetail]:
data = await user_service.get_userinfo(db=db, pk=pk)
return response_base.success(data=data)
@router.get('/{pk}/roles', summary='获取用户所有角色', dependencies=[DependsJwtAuth])
async def get_user_roles(
db: CurrentSession, pk: Annotated[int, Path(description='用户 ID')]
) -> ResponseSchemaModel[list[GetRoleDetail]]:
data = await user_service.get_roles(db=db, pk=pk)
return response_base.success(data=data)
@router.get(
'',
summary='分页获取所有用户',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_users_paginated(
db: CurrentSession,
dept: Annotated[int | None, Query(description='部门 ID')] = None,
username: Annotated[str | None, Query(description='用户名')] = None,
phone: Annotated[str | None, Query(description='手机号')] = None,
status: Annotated[int | None, Query(description='状态')] = None,
) -> ResponseSchemaModel[PageData[GetUserInfoWithRelationDetail]]:
page_data = await user_service.get_list(db=db, dept=dept, 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]:
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)
@router.put('/{pk}', summary='更新用户信息', dependencies=[DependsSuperUser])
async def update_user(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='用户 ID')],
obj: UpdateUserParam,
) -> ResponseModel:
count = await user_service.update(db=db, pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put('/{pk}/permissions', summary='更新用户权限', dependencies=[DependsSuperUser])
async def update_user_permission(
db: CurrentSessionTransaction,
request: Request,
pk: Annotated[int, Path(description='用户 ID')],
type: Annotated[UserPermissionType, Query(description='权限类型')],
) -> ResponseModel:
count = await user_service.update_permission(db=db, request=request, pk=pk, type=type)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put('/me/password', summary='更新当前用户密码', dependencies=[DependsJwtAuth])
async def update_user_password(
db: CurrentSessionTransaction, request: Request, obj: ResetPasswordParam
) -> ResponseModel:
count = await user_service.update_password(db=db, request=request, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put('/{pk}/password', summary='重置用户密码', dependencies=[DependsSuperUser])
async def reset_user_password(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='用户 ID')],
password: Annotated[str, Body(embed=True, description='新密码')],
) -> ResponseModel:
count = await user_service.reset_password(db=db, pk=pk, password=password)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put('/me/nickname', summary='更新当前用户昵称', dependencies=[DependsJwtAuth])
async def update_user_nickname(
db: CurrentSessionTransaction,
request: Request,
nickname: Annotated[str, Body(embed=True, description='用户昵称')],
) -> ResponseModel:
count = await user_service.update_nickname(db=db, request=request, nickname=nickname)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put('/me/avatar', summary='更新当前用户头像', dependencies=[DependsJwtAuth])
async def update_user_avatar(
db: CurrentSessionTransaction,
request: Request,
avatar: Annotated[str, Body(embed=True, description='用户头像地址')],
) -> ResponseModel:
count = await user_service.update_avatar(db=db, request=request, avatar=avatar)
if count > 0:
return response_base.success()
return response_base.fail()
@router.put('/me/email', summary='更新当前用户邮箱', dependencies=[DependsJwtAuth])
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, request=request, captcha=captcha, email=email)
if count > 0:
return response_base.success()
return response_base.fail()
@router.delete(
path='/{pk}',
summary='删除用户',
dependencies=[
Depends(RequestPermission('sys:user:del')),
DependsRBAC,
],
)
async def delete_user(db: CurrentSessionTransaction, pk: Annotated[int, Path(description='用户 ID')]) -> ResponseModel:
count = await user_service.delete(db=db, pk=pk)
if count > 0:
return response_base.success()
return response_base.fail()
View File
+89
View File
@@ -0,0 +1,89 @@
from collections.abc import Sequence
from sqlalchemy import Select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus
from backend.app.admin.model import DataRule
from backend.app.admin.schema.data_rule import CreateDataRuleParam, UpdateDataRuleParam
class CRUDDataRule(CRUDPlus[DataRule]):
"""数据规则数据库操作类"""
async def get(self, db: AsyncSession, pk: int) -> DataRule | None:
"""
获取规则详情
:param db: 数据库会话
:param pk: 规则 ID
:return:
"""
return await self.select_model(db, pk)
async def get_select(self, name: str | None) -> Select:
"""
获取规则列表查询表达式
:param name: 规则名称
:return:
"""
filters = {}
if name is not None:
filters['name__like'] = f'%{name}%'
return await self.select_order('id', load_strategies={'scopes': 'noload'}, **filters)
async def get_by_name(self, db: AsyncSession, name: str) -> DataRule | None:
"""
通过名称获取规则
:param db: 数据库会话
:param name: 规则名称
:return:
"""
return await self.select_model_by_column(db, name=name)
async def get_all(self, db: AsyncSession) -> Sequence[DataRule]:
"""
获取所有规则
:param db: 数据库会话
:return:
"""
return await self.select_models(db)
async def create(self, db: AsyncSession, obj: CreateDataRuleParam) -> None:
"""
创建规则
:param db: 数据库会话
:param obj: 创建规则参数
:return:
"""
await self.create_model(db, obj)
async def update(self, db: AsyncSession, pk: int, obj: UpdateDataRuleParam) -> int:
"""
更新规则
:param db: 数据库会话
:param pk: 规则 ID
:param obj: 更新规则参数
:return:
"""
return await self.update_model(db, pk, obj)
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)
data_rule_dao: CRUDDataRule = CRUDDataRule(DataRule)
+117
View File
@@ -0,0 +1,117 @@
from collections.abc import Sequence
from sqlalchemy import Select, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus
from backend.app.admin.model import DataRule, DataScope
from backend.app.admin.schema.data_scope import CreateDataScopeParam, UpdateDataScopeParam, UpdateDataScopeRuleParam
class CRUDDataScope(CRUDPlus[DataScope]):
"""数据范围数据库操作类"""
async def get(self, db: AsyncSession, pk: int) -> DataScope | None:
"""
获取数据范围详情
:param db: 数据库会话
:param pk: 范围 ID
:return:
"""
return await self.select_model(db, pk)
async def get_by_name(self, db: AsyncSession, name: str) -> DataScope | None:
"""
通过名称获取数据范围
:param db: 数据库会话
:param name: 范围名称
:return:
"""
return await self.select_model_by_column(db, name=name)
async def get_with_relation(self, db: AsyncSession, pk: int) -> DataScope:
"""
获取数据范围关联数据
:param db: 数据库会话
:param pk: 范围 ID
:return:
"""
return await self.select_model(db, pk, load_strategies=['rules'])
async def get_all(self, db: AsyncSession) -> Sequence[DataScope]:
"""
获取所有数据范围
:param db: 数据库会话
:return:
"""
return await self.select_models(db)
async def get_select(self, name: str | None, status: int | None) -> Select:
"""
获取数据范围列表查询表达式
:param name: 范围名称
:param status: 范围状态
:return:
"""
filters = {}
if name is not None:
filters['name__like'] = f'%{name}%'
if status is not None:
filters['status'] = status
return await self.select_order('id', load_strategies={'rules': 'noload', 'roles': 'noload'}, **filters)
async def create(self, db: AsyncSession, obj: CreateDataScopeParam) -> None:
"""
创建数据范围
:param db: 数据库会话
:param obj: 创建数据范围参数
:return:
"""
await self.create_model(db, obj)
async def update(self, db: AsyncSession, pk: int, obj: UpdateDataScopeParam) -> int:
"""
更新数据范围
:param db: 数据库会话
:param pk: 范围 ID
:param obj: 更新数据范围参数
:return:
"""
return await self.update_model(db, pk, obj)
async def update_rules(self, db: AsyncSession, pk: int, rule_ids: UpdateDataScopeRuleParam) -> int:
"""
更新数据范围规则
:param db: 数据库会话
:param pk: 范围 ID
:param rule_ids: 数据规则 ID 列表
:return:
"""
current_data_scope = await self.get_with_relation(db, pk)
stmt = select(DataRule).where(DataRule.id.in_(rule_ids.rules))
rules = await db.execute(stmt)
current_data_scope.rules = rules.scalars().all()
return len(current_data_scope.rules)
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)
data_scope_dao: CRUDDataScope = CRUDDataScope(DataScope)
+121
View File
@@ -0,0 +1,121 @@
from collections.abc import Sequence
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus
from backend.app.admin.model import Dept
from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
from backend.common.security.permission import filter_data_permission
class CRUDDept(CRUDPlus[Dept]):
"""部门数据库操作类"""
async def get(self, db: AsyncSession, dept_id: int) -> Dept | None:
"""
获取部门详情
:param db: 数据库会话
:param dept_id: 部门 ID
:return:
"""
return await self.select_model_by_column(db, id=dept_id, del_flag=0)
async def get_by_name(self, db: AsyncSession, name: str) -> Dept | None:
"""
通过名称获取部门
:param db: 数据库会话
:param name: 部门名称
:return:
"""
return await self.select_model_by_column(db, name=name, del_flag=0)
async def get_all(
self,
request: Request,
db: AsyncSession,
name: str | None,
leader: str | None,
phone: str | None,
status: int | None,
) -> Sequence[Dept]:
"""
获取所有部门
:param request: FastAPI 请求对象
:param db: 数据库会话
:param name: 部门名称
:param leader: 负责人
:param phone: 联系电话
:param status: 部门状态
:return:
"""
filters = {'del_flag': 0}
if name is not None:
filters['name__like'] = f'%{name}%'
if leader is not None:
filters['leader__like'] = f'%{leader}%'
if phone is not None:
filters['phone__startswith'] = phone
if status is not None:
filters['status'] = status
data_filtered = await filter_data_permission(db, request)
return await self.select_models_order(db, 'sort', 'desc', data_filtered, **filters)
async def create(self, db: AsyncSession, obj: CreateDeptParam) -> None:
"""
创建部门
:param db: 数据库会话
:param obj: 创建部门参数
:return:
"""
await self.create_model(db, obj)
async def update(self, db: AsyncSession, dept_id: int, obj: UpdateDeptParam) -> int:
"""
更新部门
:param db: 数据库会话
:param dept_id: 部门 ID
:param obj: 更新部门参数
:return:
"""
return await self.update_model(db, dept_id, obj)
async def delete(self, db: AsyncSession, dept_id: int) -> int:
"""
删除部门
:param db: 数据库会话
:param dept_id: 部门 ID
:return:
"""
return await self.delete_model_by_column(db, id=dept_id, logical_deletion=True, deleted_flag_column='del_flag')
async def get_with_relation(self, db: AsyncSession, dept_id: int) -> Dept | None:
"""
获取部门及关联数据
:param db: 数据库会话
:param dept_id: 部门 ID
:return:
"""
return await self.select_model(db, dept_id, load_strategies=['users'])
async def get_children(self, db: AsyncSession, dept_id: int) -> Sequence[Dept | None]:
"""
获取子部门列表
:param db: 数据库会话
:param dept_id: 部门 ID
:return:
"""
return await self.select_models(db, parent_id=dept_id, del_flag=0)
dept_dao: CRUDDept = CRUDDept(Dept)
+64
View File
@@ -0,0 +1,64 @@
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)
+109
View File
@@ -0,0 +1,109 @@
from collections.abc import Sequence
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus
from backend.app.admin.model import Menu
from backend.app.admin.schema.menu import CreateMenuParam, UpdateMenuParam
class CRUDMenu(CRUDPlus[Menu]):
"""菜单数据库操作类"""
async def get(self, db: AsyncSession, menu_id: int) -> Menu | None:
"""
获取菜单详情
:param db: 数据库会话
:param menu_id: 菜单 ID
:return:
"""
return await self.select_model(db, menu_id)
async def get_by_title(self, db: AsyncSession, title: str) -> Menu | None:
"""
通过标题获取菜单
:param db: 数据库会话
:param title: 菜单标题
:return:
"""
return await self.select_model_by_column(db, title=title, type__ne=2)
async def get_all(self, db: AsyncSession, title: str | None, status: int | None) -> Sequence[Menu]:
"""
获取菜单列表
:param db: 数据库会话
:param title: 菜单标题
:param status: 菜单状态
:return:
"""
filters = {}
if title is not None:
filters['title__like'] = f'%{title}%'
if status is not None:
filters['status'] = status
return await self.select_models_order(db, 'sort', **filters)
async def get_sidebar(self, db: AsyncSession, menu_ids: list[int] | None) -> Sequence[Menu]:
"""
获取用户的菜单侧边栏
:param db: 数据库会话
:param menu_ids: 菜单 ID 列表
:return:
"""
filters = {'type__in': [0, 1, 3, 4]}
if menu_ids:
filters['id__in'] = menu_ids
return await self.select_models_order(db, 'sort', 'asc', **filters)
async def create(self, db: AsyncSession, obj: CreateMenuParam) -> None:
"""
创建菜单
:param db: 数据库会话
:param obj: 创建菜单参数
:return:
"""
await self.create_model(db, obj)
async def update(self, db: AsyncSession, menu_id: int, obj: UpdateMenuParam) -> int:
"""
更新菜单
:param db: 数据库会话
:param menu_id: 菜单 ID
:param obj: 更新菜单参数
:return:
"""
return await self.update_model(db, menu_id, obj)
async def delete(self, db: AsyncSession, menu_id: int) -> int:
"""
删除菜单
:param db: 数据库会话
:param menu_id: 菜单 ID
:return:
"""
return await self.delete_model(db, menu_id)
async def get_children(self, db: AsyncSession, menu_id: int) -> list[Menu | None]:
"""
获取子菜单列表
:param db: 数据库会话
:param menu_id: 菜单 ID
:return:
"""
menu = await self.select_model(db, menu_id, load_strategies=['children'])
return menu.children
menu_dao: CRUDMenu = CRUDMenu(Menu)
+74
View File
@@ -0,0 +1,74 @@
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)
+146
View File
@@ -0,0 +1,146 @@
from collections.abc import Sequence
from sqlalchemy import Select, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus
from backend.app.admin.model import DataScope, Menu, Role
from backend.app.admin.schema.role import (
CreateRoleParam,
UpdateRoleMenuParam,
UpdateRoleParam,
UpdateRoleScopeParam,
)
class CRUDRole(CRUDPlus[Role]):
"""角色数据库操作类"""
async def get(self, db: AsyncSession, role_id: int) -> Role | None:
"""
获取角色详情
:param db: 数据库会话
:param role_id: 角色 ID
:return:
"""
return await self.select_model(db, role_id)
async def get_with_relation(self, db: AsyncSession, role_id: int) -> Role | None:
"""
获取角色及关联数据
:param db: 数据库会话
:param role_id: 角色 ID
:return:
"""
return await self.select_model(db, role_id, load_strategies=['menus', 'scopes'])
async def get_all(self, db: AsyncSession) -> Sequence[Role]:
"""
获取所有角色
:param db: 数据库会话
:return:
"""
return await self.select_models(db)
async def get_select(self, name: str | None, status: int | None) -> Select:
"""
获取角色列表查询表达式
:param name: 角色名称
:param status: 角色状态
:return:
"""
filters = {}
if name is not None:
filters['name__like'] = f'%{name}%'
if status is not None:
filters['status'] = status
return await self.select_order(
'id',
load_strategies={
'users': 'noload',
'menus': 'noload',
'scopes': 'noload',
},
**filters,
)
async def get_by_name(self, db: AsyncSession, name: str) -> Role | None:
"""
通过名称获取角色
:param db: 数据库会话
:param name: 角色名称
:return:
"""
return await self.select_model_by_column(db, name=name)
async def create(self, db: AsyncSession, obj: CreateRoleParam) -> None:
"""
创建角色
:param db: 数据库会话
:param obj: 创建角色参数
:return:
"""
await self.create_model(db, obj)
async def update(self, db: AsyncSession, role_id: int, obj: UpdateRoleParam) -> int:
"""
更新角色
:param db: 数据库会话
:param role_id: 角色 ID
:param obj: 更新角色参数
:return:
"""
return await self.update_model(db, role_id, obj)
async def update_menus(self, db: AsyncSession, role_id: int, menu_ids: UpdateRoleMenuParam) -> int:
"""
更新角色菜单
:param db: 数据库会话
:param role_id: 角色 ID
:param menu_ids: 菜单 ID 列表
:return:
"""
current_role = await self.get_with_relation(db, role_id)
stmt = select(Menu).where(Menu.id.in_(menu_ids.menus))
menus = await db.execute(stmt)
current_role.menus = menus.scalars().all()
return len(current_role.menus)
async def update_scopes(self, db: AsyncSession, role_id: int, scope_ids: UpdateRoleScopeParam) -> int:
"""
更新角色数据范围
:param db: 数据库会话
:param role_id: 角色 ID
:param scope_ids: 权限范围 ID 列表
:return:
"""
current_role = await self.get_with_relation(db, role_id)
stmt = select(DataScope).where(DataScope.id.in_(scope_ids.scopes))
scopes = await db.execute(stmt)
current_role.scopes = scopes.scalars().all()
return len(current_role.scopes)
async def delete(self, db: AsyncSession, role_ids: list[int]) -> int:
"""
批量删除角色
:param db: 数据库会话
:param role_ids: 角色 ID 列表
:return:
"""
return await self.delete_model_by_column(db, allow_multiple=True, id__in=role_ids)
role_dao: CRUDRole = CRUDRole(Role)
+289
View File
@@ -0,0 +1,289 @@
import bcrypt
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import noload, selectinload
from sqlalchemy.sql import Select
from sqlalchemy_crud_plus import CRUDPlus
from backend.app.admin.model import Dept, Role, User
from backend.app.admin.schema.user import (
AddOAuth2UserParam,
AddUserParam,
UpdateUserParam,
)
from backend.common.security.jwt import get_hash_password
from backend.utils.timezone import timezone
class CRUDUser(CRUDPlus[User]):
"""用户数据库操作类"""
async def get(self, db: AsyncSession, user_id: int) -> User | None:
"""
获取用户详情
:param db: 数据库会话
:param user_id: 用户 ID
:return:
"""
return await self.select_model(db, user_id)
async def get_by_username(self, db: AsyncSession, username: str) -> User | None:
"""
通过用户名获取用户
:param db: 数据库会话
:param username: 用户名
:return:
"""
return await self.select_model_by_column(db, username=username)
async def get_by_nickname(self, db: AsyncSession, nickname: str) -> User | None:
"""
通过昵称获取用户
:param db: 数据库会话
:param nickname: 用户昵称
:return:
"""
return await self.select_model_by_column(db, nickname=nickname)
async def update_login_time(self, db: AsyncSession, username: str) -> int:
"""
更新用户最后登录时间
:param db: 数据库会话
:param username: 用户名
:return:
"""
return await self.update_model_by_column(db, {'last_login_time': timezone.now()}, username=username)
async def add(self, db: AsyncSession, obj: AddUserParam) -> None:
"""
添加用户
:param db: 数据库会话
:param obj: 添加用户参数
:return:
"""
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)
stmt = select(Role).where(Role.id.in_(obj.roles))
roles = await db.execute(stmt)
new_user.roles = roles.scalars().all()
db.add(new_user)
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})
new_user = self.model(**dict_obj)
stmt = select(Role)
role = await db.execute(stmt)
new_user.roles = [role.scalars().first()] # 默认绑定第一个角色
db.add(new_user)
async def update(self, db: AsyncSession, input_user: User, obj: UpdateUserParam) -> int:
"""
更新用户信息
:param db: 数据库会话
:param input_user: 用户 ID
:param obj: 更新用户参数
:return:
"""
role_ids = obj.roles
del obj.roles
count = await self.update_model(db, input_user.id, obj)
stmt = select(Role).where(Role.id.in_(role_ids))
roles = await db.execute(stmt)
input_user.roles = roles.scalars().all()
return count
async def update_nickname(self, db: AsyncSession, user_id: int, nickname: str) -> int:
"""
更新用户昵称
:param db: 数据库会话
:param user_id: 用户 ID
:param nickname: 用户昵称
:return:
"""
return await self.update_model(db, user_id, {'nickname': nickname})
async def update_avatar(self, db: AsyncSession, user_id: int, avatar: str) -> int:
"""
更新用户头像
:param db: 数据库会话
:param user_id: 用户 ID
:param avatar: 头像地址
:return:
"""
return await self.update_model(db, user_id, {'avatar': avatar})
async def update_email(self, db: AsyncSession, user_id: int, email: str) -> int:
"""
更新用户邮箱
:param db: 数据库会话
:param user_id: 用户 ID
:param email: 邮箱
:return:
"""
return await self.update_model(db, user_id, {'email': email})
async def delete(self, db: AsyncSession, user_id: int) -> int:
"""
删除用户
:param db: 数据库会话
:param user_id: 用户 ID
:return:
"""
return await self.delete_model(db, user_id)
async def check_email(self, db: AsyncSession, email: str) -> User | None:
"""
检查邮箱是否已被绑定
:param db: 数据库会话
:param email: 电子邮箱
:return:
"""
return await self.select_model_by_column(db, email=email)
async def reset_password(self, db: AsyncSession, pk: int, password: str) -> int:
"""
重置用户密码
:param db: 数据库会话
:param pk: 用户 ID
:param password: 新密码
:return:
"""
salt = bcrypt.gensalt()
new_pwd = get_hash_password(password, salt)
return await self.update_model(db, pk, {'password': new_pwd, 'salt': salt})
async def get_select(self, dept: int | None, username: str | None, phone: str | None, status: int | None) -> Select:
"""
获取用户列表查询表达式
:param dept: 部门 ID
:param username: 用户名
:param phone: 电话号码
:param status: 用户状态
:return:
"""
filters = {}
if dept:
filters['dept_id'] = dept
if username:
filters['username__like'] = f'%{username}%'
if phone:
filters['phone__like'] = f'%{phone}%'
if status is not None:
filters['status'] = status
return await self.select_order(
'id',
'desc',
load_options=[
selectinload(self.model.dept).options(noload(Dept.parent), noload(Dept.children), noload(Dept.users)),
selectinload(self.model.roles).options(noload(Role.users), noload(Role.menus), noload(Role.scopes)),
],
**filters,
)
async def set_super(self, db: AsyncSession, user_id: int, *, is_super: bool) -> int:
"""
设置用户超级管理员状态
:param db: 数据库会话
:param user_id: 用户 ID
:param is_super: 是否超级管理员
:return:
"""
return await self.update_model(db, user_id, {'is_superuser': is_super})
async def set_staff(self, db: AsyncSession, user_id: int, *, is_staff: bool) -> int:
"""
设置用户后台登录状态
:param db: 数据库会话
:param user_id: 用户 ID
:param is_staff: 是否可登录后台
:return:
"""
return await self.update_model(db, user_id, {'is_staff': is_staff})
async def set_status(self, db: AsyncSession, user_id: int, status: int) -> int:
"""
设置用户状态
:param db: 数据库会话
:param user_id: 用户 ID
:param status: 状态
:return:
"""
return await self.update_model(db, user_id, {'status': status})
async def set_multi_login(self, db: AsyncSession, user_id: int, *, multi_login: bool) -> int:
"""
设置用户多端登录状态
:param db: 数据库会话
:param user_id: 用户 ID
:param multi_login: 是否允许多端登录
:return:
"""
return await self.update_model(db, user_id, {'is_multi_login': multi_login})
async def get_with_relation(
self,
db: AsyncSession,
*,
user_id: int | None = None,
username: str | None = None,
) -> User | None:
"""
获取用户关联信息
:param db: 数据库会话
:param user_id: 用户 ID
:param username: 用户名
:return:
"""
filters = {}
if user_id:
filters['id'] = user_id
if username:
filters['username'] = username
return await self.select_model_by_column(
db,
load_options=[selectinload(self.model.roles).options(selectinload(Role.menus), selectinload(Role.scopes))],
load_strategies=['dept'],
**filters,
)
user_dao: CRUDUser = CRUDUser(User)
+8
View File
@@ -0,0 +1,8 @@
from backend.app.admin.model.data_rule import DataRule as DataRule
from backend.app.admin.model.data_scope import DataScope as DataScope
from backend.app.admin.model.dept import Dept as Dept
from backend.app.admin.model.login_log import LoginLog as LoginLog
from backend.app.admin.model.menu import Menu as Menu
from backend.app.admin.model.opera_log import OperaLog as OperaLog
from backend.app.admin.model.role import Role as Role
from backend.app.admin.model.user import User as User
+31
View File
@@ -0,0 +1,31 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_data_scope_rule
from backend.common.model import Base, id_key
if TYPE_CHECKING:
from backend.app.admin.model import DataScope
class DataRule(Base):
"""数据规则表"""
__tablename__ = 'sys_data_rule'
id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(500), unique=True, comment='名称')
model: Mapped[str] = mapped_column(String(50), comment='SQLA 模型名,对应 DATA_PERMISSION_MODELS 键名')
column: Mapped[str] = mapped_column(String(20), comment='模型字段名')
operator: Mapped[int] = mapped_column(comment='运算符(0and、1or')
expression: Mapped[int] = mapped_column(
comment='表达式(0==、1!=、2>、3>=、4<、5<=、6in、7not_in',
)
value: Mapped[str] = mapped_column(String(255), comment='规则值')
# 数据范围规则多对多
scopes: Mapped[list[DataScope]] = relationship(init=False, secondary=sys_data_scope_rule, back_populates='rules')
+28
View File
@@ -0,0 +1,28 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_data_scope_rule, sys_role_data_scope
from backend.common.model import Base, id_key
if TYPE_CHECKING:
from backend.app.admin.model import DataRule, Role
class DataScope(Base):
"""数据范围表"""
__tablename__ = 'sys_data_scope'
id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(50), unique=True, comment='名称')
status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)')
# 数据范围规则多对多
rules: Mapped[list[DataRule]] = relationship(init=False, secondary=sys_data_scope_rule, back_populates='scopes')
# 角色数据范围多对多
roles: Mapped[list[Role]] = relationship(init=False, secondary=sys_role_data_scope, back_populates='scopes')
+45
View File
@@ -0,0 +1,45 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, Boolean, ForeignKey, String
from sqlalchemy.dialects.postgresql import INTEGER
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.common.model import Base, id_key
if TYPE_CHECKING:
from backend.app.admin.model import User
class Dept(Base):
"""部门表"""
__tablename__ = 'sys_dept'
id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(50), comment='部门名称')
sort: Mapped[int] = mapped_column(default=0, comment='排序')
leader: Mapped[str | None] = mapped_column(String(20), default=None, comment='负责人')
phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机')
email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱')
status: Mapped[int] = mapped_column(default=1, comment='部门状态(0停用 1正常)')
del_flag: Mapped[bool] = mapped_column(
Boolean().with_variant(INTEGER, 'postgresql'),
default=False,
comment='删除标志(0删除 1存在)',
)
# 父级部门一对多
parent_id: Mapped[int | None] = mapped_column(
BigInteger,
ForeignKey('sys_dept.id', ondelete='SET NULL'),
default=None,
index=True,
comment='父部门ID',
)
parent: Mapped[Dept | None] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[list[Dept] | None] = relationship(init=False, back_populates='parent')
# 部门用户一对多
users: Mapped[list[User]] = relationship(init=False, back_populates='dept')
@@ -1,13 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime
from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column
from backend.app.models.base import DataClassBase, id_key
from backend.app.utils.timezone import timezone
from backend.common.model import DataClassBase, TimeZone, id_key
from backend.utils.timezone import timezone
class LoginLog(DataClassBase):
@@ -27,6 +26,11 @@ class LoginLog(DataClassBase):
os: Mapped[str | None] = mapped_column(String(50), comment='操作系统')
browser: Mapped[str | None] = mapped_column(String(50), comment='浏览器')
device: Mapped[str | None] = mapped_column(String(50), comment='设备')
msg: Mapped[str] = mapped_column(LONGTEXT, comment='提示消息')
login_time: Mapped[datetime] = mapped_column(comment='登录时间')
created_time: Mapped[datetime] = mapped_column(init=False, default_factory=timezone.now, comment='创建时间')
msg: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='提示消息')
login_time: Mapped[datetime] = mapped_column(TimeZone, comment='登录时间')
created_time: Mapped[datetime] = mapped_column(
TimeZone,
init=False,
default_factory=timezone.now,
comment='创建时间',
)
+53
View File
@@ -0,0 +1,53 @@
from sqlalchemy import BigInteger, Column, ForeignKey, Table
from backend.common.model import MappedBase
sys_user_role = Table(
'sys_user_role',
MappedBase.metadata,
Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column('user_id', BigInteger, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'),
Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'),
)
sys_role_menu = Table(
'sys_role_menu',
MappedBase.metadata,
Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'),
Column('menu_id', BigInteger, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'),
)
sys_role_data_scope = Table(
'sys_role_data_scope',
MappedBase.metadata,
Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键 ID'),
Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色 ID'),
Column(
'data_scope_id',
BigInteger,
ForeignKey('sys_data_scope.id', ondelete='CASCADE'),
primary_key=True,
comment='数据范围 ID',
),
)
sys_data_scope_rule = Table(
'sys_data_scope_rule',
MappedBase.metadata,
Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column(
'data_scope_id',
BigInteger,
ForeignKey('sys_data_scope.id', ondelete='CASCADE'),
primary_key=True,
comment='数据范围 ID',
),
Column(
'data_rule_id',
BigInteger,
ForeignKey('sys_data_rule.id', ondelete='CASCADE'),
primary_key=True,
comment='数据规则 ID',
),
)
+57
View File
@@ -0,0 +1,57 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, ForeignKey, String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_role_menu
from backend.common.model import Base, id_key
if TYPE_CHECKING:
from backend.app.admin.model import Role
class Menu(Base):
"""菜单表"""
__tablename__ = 'sys_menu'
id: Mapped[id_key] = mapped_column(init=False)
title: Mapped[str] = mapped_column(String(50), comment='菜单标题')
name: Mapped[str] = mapped_column(String(50), comment='菜单名称')
path: Mapped[str | None] = mapped_column(String(200), comment='路由地址')
sort: Mapped[int] = mapped_column(default=0, comment='排序')
icon: Mapped[str | None] = mapped_column(String(100), default=None, comment='菜单图标')
type: Mapped[int] = mapped_column(default=0, comment='菜单类型(0目录 1菜单 2按钮 3内嵌 4外链)')
component: Mapped[str | None] = mapped_column(String(255), default=None, comment='组件路径')
perms: Mapped[str | None] = mapped_column(String(100), default=None, comment='权限标识')
status: Mapped[int] = mapped_column(default=1, comment='菜单状态(0停用 1正常)')
display: Mapped[int] = mapped_column(default=1, comment='是否显示(0否 1是)')
cache: Mapped[int] = mapped_column(default=1, comment='是否缓存(0否 1是)')
link: Mapped[str | None] = mapped_column(
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='外链地址',
)
remark: Mapped[str | None] = mapped_column(
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
)
# 父级菜单一对多
parent_id: Mapped[int | None] = mapped_column(
BigInteger,
ForeignKey('sys_menu.id', ondelete='SET NULL'),
default=None,
index=True,
comment='父菜单ID',
)
parent: Mapped[Menu | None] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[list[Menu] | None] = relationship(init=False, back_populates='parent')
# 菜单角色多对多
roles: Mapped[list[Role]] = relationship(init=False, secondary=sys_role_menu, back_populates='menus')
@@ -1,13 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime
from sqlalchemy import String
from sqlalchemy.dialects.mysql import JSON, LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column
from backend.app.models.base import DataClassBase, id_key
from backend.app.utils.timezone import timezone
from backend.common.model import DataClassBase, TimeZone, id_key
from backend.utils.timezone import timezone
class OperaLog(DataClassBase):
@@ -16,6 +15,7 @@ class OperaLog(DataClassBase):
__tablename__ = 'sys_opera_log'
id: Mapped[id_key] = mapped_column(init=False)
trace_id: Mapped[str] = mapped_column(String(32), comment='请求跟踪 ID')
username: Mapped[str | None] = mapped_column(String(20), comment='用户名')
method: Mapped[str] = mapped_column(String(20), comment='请求类型')
title: Mapped[str] = mapped_column(String(255), comment='操作模块')
@@ -24,14 +24,19 @@ class OperaLog(DataClassBase):
country: Mapped[str | None] = mapped_column(String(50), comment='国家')
region: Mapped[str | None] = mapped_column(String(50), comment='地区')
city: Mapped[str | None] = mapped_column(String(50), comment='城市')
user_agent: Mapped[str] = mapped_column(String(255), comment='请求头')
user_agent: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='请求头')
os: Mapped[str | None] = mapped_column(String(50), comment='操作系统')
browser: Mapped[str | None] = mapped_column(String(50), comment='浏览器')
device: Mapped[str | None] = mapped_column(String(50), comment='设备')
args: Mapped[str | None] = mapped_column(JSON(), comment='请求参数')
status: Mapped[int] = mapped_column(comment='操作状态(0异常 1正常)')
code: Mapped[str] = mapped_column(String(20), insert_default='200', comment='操作状态码')
msg: Mapped[str | None] = mapped_column(LONGTEXT, comment='提示消息')
cost_time: Mapped[float] = mapped_column(insert_default=0.0, comment='请求耗时ms')
opera_time: Mapped[datetime] = mapped_column(comment='操作时间')
created_time: Mapped[datetime] = mapped_column(init=False, default_factory=timezone.now, comment='创建时间')
msg: Mapped[str | None] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), 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='创建时间',
)
+43
View File
@@ -0,0 +1,43 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from sqlalchemy import Boolean, String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import INTEGER, TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_role_data_scope, sys_role_menu, sys_user_role
from backend.common.model import Base, id_key
if TYPE_CHECKING:
from backend.app.admin.model import DataScope, Menu, User
class Role(Base):
"""角色表"""
__tablename__ = 'sys_role'
id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称')
status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)')
is_filter_scopes: Mapped[bool] = mapped_column(
Boolean().with_variant(INTEGER, 'postgresql'),
default=True,
comment='过滤数据权限(0否 1是)',
)
remark: Mapped[str | None] = mapped_column(
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
)
# 角色用户多对多
users: Mapped[list[User]] = relationship(init=False, secondary=sys_user_role, back_populates='roles')
# 角色菜单多对多
menus: Mapped[list[Menu]] = relationship(init=False, secondary=sys_role_menu, back_populates='roles')
# 角色数据范围多对多
scopes: Mapped[list[DataScope]] = relationship(init=False, secondary=sys_role_data_scope, back_populates='roles')
+66
View File
@@ -0,0 +1,66 @@
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import VARBINARY, Boolean, ForeignKey, String
from sqlalchemy.dialects.postgresql import BYTEA, INTEGER
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_user_role
from backend.common.model import Base, TimeZone, id_key
from backend.database.db import uuid4_str
from backend.utils.timezone import timezone
if TYPE_CHECKING:
from backend.app.admin.model import Dept, Role
class User(Base):
"""用户表"""
__tablename__ = 'sys_user'
id: Mapped[id_key] = mapped_column(init=False)
uuid: Mapped[str] = mapped_column(String(50), init=False, default_factory=uuid4_str, unique=True)
username: Mapped[str] = mapped_column(String(20), unique=True, index=True, comment='用户名')
nickname: Mapped[str] = mapped_column(String(20), comment='昵称')
password: Mapped[str | None] = mapped_column(String(255), comment='密码')
salt: Mapped[bytes | None] = mapped_column(VARBINARY(255).with_variant(BYTEA(255), 'postgresql'), comment='加密盐')
email: Mapped[str | None] = mapped_column(String(50), default=None, unique=True, index=True, comment='邮箱')
phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机号')
avatar: Mapped[str | None] = mapped_column(String(255), default=None, comment='头像')
status: Mapped[int] = mapped_column(default=1, index=True, comment='用户账号状态(0停用 1正常)')
is_superuser: Mapped[bool] = mapped_column(
Boolean().with_variant(INTEGER, 'postgresql'),
default=False,
comment='超级权限(0否 1是)',
)
is_staff: Mapped[bool] = mapped_column(
Boolean().with_variant(INTEGER, 'postgresql'),
default=False,
comment='后台管理登陆(0否 1是)',
)
is_multi_login: Mapped[bool] = mapped_column(
Boolean().with_variant(INTEGER, 'postgresql'),
default=False,
comment='是否重复登陆(0否 1是)',
)
join_time: Mapped[datetime] = mapped_column(TimeZone, init=False, default_factory=timezone.now, comment='注册时间')
last_login_time: Mapped[datetime | None] = mapped_column(
TimeZone,
init=False,
onupdate=timezone.now,
comment='上次登录',
)
# 部门用户一对多
dept_id: Mapped[int | None] = mapped_column(
ForeignKey('sys_dept.id', ondelete='SET NULL'),
default=None,
comment='部门关联ID',
)
dept: Mapped[Dept | None] = relationship(init=False, back_populates='users')
# 用户角色多对多
roles: Mapped[list[Role]] = relationship(init=False, secondary=sys_user_role, back_populates='users')
+11
View File
@@ -0,0 +1,11 @@
from pydantic import Field
from backend.common.schema import SchemaBase
class GetCaptchaDetail(SchemaBase):
"""验证码详情"""
uuid: str = Field(description='图片唯一标识')
img_type: str = Field(description='图片类型')
image: str = Field(description='图片内容')
+48
View File
@@ -0,0 +1,48 @@
from datetime import datetime
from pydantic import ConfigDict, Field
from backend.common.enums import RoleDataRuleExpressionType, RoleDataRuleOperatorType
from backend.common.schema import SchemaBase
class DataRuleSchemaBase(SchemaBase):
"""数据规则基础模型"""
name: str = Field(description='规则名称')
model: str = Field(description='模型名称')
column: str = Field(description='字段名称')
operator: RoleDataRuleOperatorType = Field(description='操作符(AND/OR')
expression: RoleDataRuleExpressionType = Field(description='表达式类型')
value: str = Field(description='规则值')
class CreateDataRuleParam(DataRuleSchemaBase):
"""创建数据规则参数"""
class UpdateDataRuleParam(DataRuleSchemaBase):
"""更新数据规则参数"""
class DeleteDataRuleParam(SchemaBase):
"""删除数据规则参数"""
pks: list[int] = Field(description='规则 ID 列表')
class GetDataRuleDetail(DataRuleSchemaBase):
"""数据规则详情"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description='规则 ID')
created_time: datetime = Field(description='创建时间')
updated_time: datetime | None = Field(None, description='更新时间')
class GetDataRuleColumnDetail(SchemaBase):
"""数据规则可用模型字段详情"""
key: str = Field(description='字段名')
comment: str = Field(description='字段评论')
+50
View File
@@ -0,0 +1,50 @@
from datetime import datetime
from pydantic import ConfigDict, Field
from backend.app.admin.schema.data_rule import GetDataRuleDetail
from backend.common.enums import StatusType
from backend.common.schema import SchemaBase
class DataScopeBase(SchemaBase):
"""数据范围基础模型"""
name: str = Field(description='名称')
status: StatusType = Field(description='状态')
class CreateDataScopeParam(DataScopeBase):
"""创建数据范围参数"""
class UpdateDataScopeParam(DataScopeBase):
"""更新数据范围参数"""
class UpdateDataScopeRuleParam(SchemaBase):
"""更新数据范围规则参数"""
rules: list[int] = Field(description='数据规则 ID 列表')
class DeleteDataScopeParam(SchemaBase):
"""删除数据范围参数"""
pks: list[int] = Field(description='数据范围 ID 列表')
class GetDataScopeDetail(DataScopeBase):
"""数据范围详情"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description='数据范围 ID')
created_time: datetime = Field(description='创建时间')
updated_time: datetime | None = Field(None, description='更新时间')
class GetDataScopeWithRelationDetail(GetDataScopeDetail):
"""数据范围关联详情"""
rules: list[GetDataRuleDetail] = Field([], description='数据规则列表')
+43
View File
@@ -0,0 +1,43 @@
from datetime import datetime
from pydantic import ConfigDict, Field
from backend.common.enums import StatusType
from backend.common.schema import CustomEmailStr, CustomPhoneNumber, SchemaBase
class DeptSchemaBase(SchemaBase):
"""部门基础模型"""
name: str = Field(description='部门名称')
parent_id: int | None = Field(None, description='部门父级 ID')
sort: int = Field(0, ge=0, description='排序')
leader: str | None = Field(None, description='负责人')
phone: CustomPhoneNumber | None = Field(None, description='联系电话')
email: CustomEmailStr | None = Field(None, description='邮箱')
status: StatusType = Field(description='状态')
class CreateDeptParam(DeptSchemaBase):
"""创建部门参数"""
class UpdateDeptParam(DeptSchemaBase):
"""更新部门参数"""
class GetDeptDetail(DeptSchemaBase):
"""部门详情"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description='部门 ID')
del_flag: bool = Field(description='是否删除')
created_time: datetime = Field(description='创建时间')
updated_time: datetime | None = Field(None, description='更新时间')
class GetDeptTree(GetDeptDetail):
"""获取部门树"""
children: list['GetDeptTree'] | None = Field(None, description='子菜单')
+46
View File
@@ -0,0 +1,46 @@
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 = 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='创建时间')
+49
View File
@@ -0,0 +1,49 @@
from datetime import datetime
from pydantic import ConfigDict, Field
from backend.common.enums import MenuType, StatusType
from backend.common.schema import SchemaBase
class MenuSchemaBase(SchemaBase):
"""菜单基础模型"""
title: str = Field(description='菜单标题')
name: str = Field(description='菜单名称')
path: str | None = Field(None, description='路由地址')
parent_id: int | None = Field(None, description='菜单父级 ID')
sort: int = Field(0, ge=0, description='排序')
icon: str | None = Field(None, description='图标')
type: MenuType = Field(description='菜单类型(0目录 1菜单 2按钮 3内嵌 4外链)')
component: str | None = Field(None, description='组件路径')
perms: str | None = Field(None, description='权限标识')
status: StatusType = Field(description='状态')
display: StatusType = Field(description='是否显示')
cache: StatusType = Field(description='是否缓存')
link: str | None = Field(None, description='外链地址')
remark: str | None = Field(None, description='备注')
class CreateMenuParam(MenuSchemaBase):
"""创建菜单参数"""
class UpdateMenuParam(MenuSchemaBase):
"""更新菜单参数"""
class GetMenuDetail(MenuSchemaBase):
"""菜单详情"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description='菜单 ID')
created_time: datetime = Field(description='创建时间')
updated_time: datetime | None = Field(None, description='更新时间')
class GetMenuTree(GetMenuDetail):
"""获取菜单树"""
children: list['GetMenuTree'] | None = Field(None, description='子菜单')
+54
View File
@@ -0,0 +1,54 @@
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 = 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='创建时间')
+60
View File
@@ -0,0 +1,60 @@
from datetime import datetime
from pydantic import ConfigDict, Field
from backend.app.admin.schema.data_scope import GetDataScopeDetail
from backend.app.admin.schema.menu import GetMenuDetail
from backend.common.enums import StatusType
from backend.common.schema import SchemaBase
class RoleSchemaBase(SchemaBase):
"""角色基础模型"""
name: str = Field(description='角色名称')
status: StatusType = Field(description='状态')
is_filter_scopes: bool = Field(True, description='过滤数据权限')
remark: str | None = Field(None, description='备注')
class CreateRoleParam(RoleSchemaBase):
"""创建角色参数"""
class UpdateRoleParam(RoleSchemaBase):
"""更新角色参数"""
class DeleteRoleParam(SchemaBase):
"""删除角色参数"""
pks: list[int] = Field(description='角色 ID 列表')
class UpdateRoleMenuParam(SchemaBase):
"""更新角色菜单参数"""
menus: list[int] = Field(description='菜单 ID 列表')
class UpdateRoleScopeParam(SchemaBase):
"""更新角色数据范围参数"""
scopes: list[int] = Field(description='数据范围 ID 列表')
class GetRoleDetail(RoleSchemaBase):
"""角色详情"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description='角色 ID')
created_time: datetime = Field(description='创建时间')
updated_time: datetime | None = Field(None, description='更新时间')
class GetRoleWithRelationDetail(GetRoleDetail):
"""角色关联详情"""
menus: list[GetMenuDetail | None] = Field([], description='菜单详情列表')
scopes: list[GetDataScopeDetail | None] = Field([], description='数据范围列表')
+49
View File
@@ -0,0 +1,49 @@
from datetime import datetime
from pydantic import Field
from backend.app.admin.schema.user import GetUserInfoDetail
from backend.common.enums import StatusType
from backend.common.schema import SchemaBase
class GetSwaggerToken(SchemaBase):
"""Swagger 认证令牌"""
access_token: str = Field(description='访问令牌')
token_type: str = Field('Bearer', description='令牌类型')
user: GetUserInfoDetail = Field(description='用户信息')
class AccessTokenBase(SchemaBase):
"""访问令牌基础模型"""
access_token: str = Field(description='访问令牌')
access_token_expire_time: datetime = Field(description='令牌过期时间')
session_uuid: str = Field(description='会话 UUID')
class GetNewToken(AccessTokenBase):
"""获取新令牌"""
class GetLoginToken(AccessTokenBase):
"""获取登录令牌"""
user: GetUserInfoDetail = Field(description='用户信息')
class GetTokenDetail(SchemaBase):
"""令牌详情"""
id: int = Field(description='用户 ID')
session_uuid: str = Field(description='会话 UUID')
username: str = Field(description='用户名')
nickname: str = Field(description='昵称')
ip: str = Field(description='IP 地址')
os: str = Field(description='操作系统')
browser: str = Field(description='浏览器')
device: str = Field(description='设备')
status: StatusType = Field(description='状态')
last_login_time: str = Field(description='最后登录时间')
expire_time: datetime = Field(description='过期时间')
+114
View File
@@ -0,0 +1,114 @@
from datetime import datetime
from typing import Any
from pydantic import ConfigDict, Field, HttpUrl, model_validator
from typing_extensions import Self
from backend.app.admin.schema.dept import GetDeptDetail
from backend.app.admin.schema.role import GetRoleWithRelationDetail
from backend.common.enums import StatusType
from backend.common.schema import CustomEmailStr, CustomPhoneNumber, SchemaBase
class AuthSchemaBase(SchemaBase):
"""用户认证基础模型"""
username: str = Field(description='用户名')
password: str = Field(description='密码')
class AuthLoginParam(AuthSchemaBase):
"""用户登录参数"""
uuid: str = Field(description='验证码 UUID')
captcha: str = Field(description='验证码')
class AddUserParam(AuthSchemaBase):
"""添加用户参数"""
nickname: str | None = Field(None, description='昵称')
email: CustomEmailStr | None = Field(None, description='邮箱')
phone: CustomPhoneNumber | None = Field(None, description='手机号码')
dept_id: int = Field(description='部门 ID')
roles: list[int] = Field(description='角色 ID 列表')
class AddOAuth2UserParam(AuthSchemaBase):
"""添加 OAuth2 用户参数"""
password: str | None = Field(None, description='密码')
nickname: str | None = Field(None, description='昵称')
email: CustomEmailStr | None = Field(None, description='邮箱')
avatar: HttpUrl | None = Field(None, description='头像地址')
class ResetPasswordParam(SchemaBase):
"""重置密码参数"""
old_password: str = Field(description='旧密码')
new_password: str = Field(description='新密码')
confirm_password: str = Field(description='确认密码')
class UserInfoSchemaBase(SchemaBase):
"""用户信息基础模型"""
dept_id: int | None = Field(None, description='部门 ID')
username: str = Field(description='用户名')
nickname: str = Field(description='昵称')
avatar: HttpUrl | None = Field(None, description='头像地址')
email: CustomEmailStr | None = Field(None, description='邮箱')
phone: CustomPhoneNumber | None = Field(None, description='手机号')
class UpdateUserParam(UserInfoSchemaBase):
"""更新用户参数"""
roles: list[int] = Field(description='角色 ID 列表')
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='状态')
is_superuser: bool = Field(description='是否超级管理员')
is_staff: bool = Field(description='是否管理员')
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)
dept: GetDeptDetail | None = Field(None, description='部门信息')
roles: list[GetRoleWithRelationDetail] = Field(description='角色列表')
class GetCurrentUserInfoWithRelationDetail(GetUserInfoWithRelationDetail):
"""当前用户信息关联详情"""
model_config = ConfigDict(from_attributes=True)
dept: str | None = Field(None, description='部门名称')
roles: list[str] = Field(description='角色名称列表')
@model_validator(mode='before')
@classmethod
def handel(cls, data: Any) -> Self:
"""处理部门和角色数据"""
dept = data['dept']
if dept:
data['dept'] = dept['name']
roles = data['roles']
if roles:
data['roles'] = [role['name'] for role in roles]
return data
+260
View File
@@ -0,0 +1,260 @@
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_menu import menu_dao
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.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
from backend.common.response.response_code import CustomErrorCode
from backend.common.security.jwt import (
create_access_token,
create_new_token,
create_refresh_token,
get_token,
jwt_decode,
password_verify,
)
from backend.core.conf import settings
from backend.database.db import uuid4_str
from backend.database.redis import redis_client
from backend.utils.timezone import timezone
class AuthService:
"""认证服务类"""
@staticmethod
async def user_verify(db: AsyncSession, username: str, password: str) -> User:
"""
验证用户名和密码
:param db: 数据库会话
:param username: 用户名
:param password: 密码
:return:
"""
user = await user_dao.get_by_username(db, username)
if not user:
raise errors.NotFoundError(msg='用户名或密码有误')
if user.password is None:
raise errors.AuthorizationError(msg='用户名或密码有误')
if not password_verify(password, user.password):
raise errors.AuthorizationError(msg='用户名或密码有误')
if not user.status:
raise errors.AuthorizationError(msg='用户已被锁定, 请联系统管理员')
return user
async def swagger_login(self, *, db: AsyncSession, obj: HTTPBasicCredentials) -> tuple[str, User]:
"""
Swagger 文档登录
:param db: 数据库会话
:param obj: 登录凭证
:return:
"""
user = await self.user_verify(db, obj.username, obj.password)
await user_dao.update_login_time(db, obj.username)
access_token = await create_access_token(
user.id,
multi_login=user.is_multi_login,
# extra info
swagger=True,
)
return access_token.access_token, user
async def login(
self,
*,
db: AsyncSession,
response: Response,
obj: AuthLoginParam,
background_tasks: BackgroundTasks,
) -> GetLoginToken:
"""
用户登录
:param db: 数据库会话
:param request: 请求对象
:param response: 响应对象
:param obj: 登录参数
:param background_tasks: 后台任务
:return:
"""
user = None
try:
user = await self.user_verify(db, obj.username, obj.password)
captcha_code = await redis_client.get(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{obj.uuid}')
if not captcha_code:
raise errors.RequestError(msg=t('error.captcha.expired'))
if captcha_code.lower() != obj.captcha.lower():
raise errors.CustomError(error=CustomErrorCode.CAPTCHA_ERROR)
await redis_client.delete(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{obj.uuid}')
await user_dao.update_login_time(db, obj.username)
await db.refresh(user)
access_token = await create_access_token(
user.id,
multi_login=user.is_multi_login,
# extra info
username=user.username,
nickname=user.nickname,
last_login_time=timezone.to_str(user.last_login_time),
ip=ctx.ip,
os=ctx.os,
browser=ctx.browser,
device=ctx.device,
)
refresh_token = await create_refresh_token(
access_token.session_uuid,
user.id,
multi_login=user.is_multi_login,
)
response.set_cookie(
key=settings.COOKIE_REFRESH_TOKEN_KEY,
value=refresh_token.refresh_token,
max_age=settings.COOKIE_REFRESH_TOKEN_EXPIRE_SECONDS,
expires=timezone.to_utc(refresh_token.refresh_token_expire_time),
httponly=True,
)
except errors.NotFoundError as e:
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,
db=db,
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)
except Exception as e:
log.error(f'登陆错误: {e}')
raise
else:
background_tasks.add_task(
login_log_service.create,
db=db,
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.access_token,
access_token_expire_time=access_token.access_token_expire_time,
session_uuid=access_token.session_uuid,
user=user, # type: ignore
)
return data
@staticmethod
async def get_codes(*, db: AsyncSession, request: Request) -> list[str]:
"""
获取用户权限码
:param db: 数据库会话
:param request: FastAPI 请求对象
:return:
"""
codes = set()
if request.user.is_superuser:
menus = await menu_dao.get_all(db, None, None)
for menu in menus:
if menu.perms:
codes.add(*menu.perms.split(','))
else:
roles = request.user.roles
if roles:
for role in roles:
for menu in role.menus:
if menu.perms:
codes.add(*menu.perms.split(','))
return list(codes)
@staticmethod
async def refresh_token(*, db: AsyncSession, request: Request) -> GetNewToken:
"""
刷新令牌
:param db: 数据库会话
:param request: FastAPI 请求对象
:return:
"""
refresh_token = request.cookies.get(settings.COOKIE_REFRESH_TOKEN_KEY)
if not refresh_token:
raise errors.RequestError(msg='Refresh Token 已过期,请重新登录')
token_payload = jwt_decode(refresh_token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
if not user.status:
raise errors.AuthorizationError(msg='用户已被锁定, 请联系统管理员')
if not user.is_multi_login and await redis_client.keys(match=f'{settings.TOKEN_REDIS_PREFIX}:{user.id}:*'):
raise errors.ForbiddenError(msg='此用户已在异地登录,请重新登录并及时修改密码')
new_token = await create_new_token(
refresh_token,
token_payload.session_uuid,
user.id,
multi_login=user.is_multi_login,
# extra info
username=user.username,
nickname=user.nickname,
last_login_time=timezone.to_str(user.last_login_time),
ip=ctx.ip,
os=ctx.os,
browser=ctx.browser,
device_type=ctx.device,
)
data = GetNewToken(
access_token=new_token.new_access_token,
access_token_expire_time=new_token.new_access_token_expire_time,
session_uuid=new_token.session_uuid,
)
return data
@staticmethod
async def logout(*, request: Request, response: Response) -> None:
"""
用户登出
:param request: FastAPI 请求对象
:param response: FastAPI 响应对象
:return:
"""
try:
token = get_token(request)
token_payload = jwt_decode(token)
user_id = token_payload.id
session_uuid = token_payload.session_uuid
refresh_token = request.cookies.get(settings.COOKIE_REFRESH_TOKEN_KEY)
except errors.TokenError:
return
finally:
response.delete_cookie(settings.COOKIE_REFRESH_TOKEN_KEY)
await redis_client.delete(f'{settings.TOKEN_REDIS_PREFIX}:{user_id}:{session_uuid}')
await redis_client.delete(f'{settings.TOKEN_EXTRA_INFO_REDIS_PREFIX}:{user_id}:{session_uuid}')
if refresh_token:
await redis_client.delete(f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user_id}:{refresh_token}')
auth_service: AuthService = AuthService()
@@ -0,0 +1,131 @@
from collections.abc import Sequence
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.admin.crud.crud_data_rule import data_rule_dao
from backend.app.admin.model import DataRule
from backend.app.admin.schema.data_rule import (
CreateDataRuleParam,
DeleteDataRuleParam,
GetDataRuleColumnDetail,
UpdateDataRuleParam,
)
from backend.common.exception import errors
from backend.common.pagination import paging_data
from backend.core.conf import settings
from backend.utils.import_parse import dynamic_import_data_model
class DataRuleService:
"""数据规则服务类"""
@staticmethod
async def get(*, db: AsyncSession, pk: int) -> DataRule:
"""
获取数据规则详情
:param db: 数据库会话
:param pk: 规则 ID
:return:
"""
data_rule = await data_rule_dao.get(db, pk)
if not data_rule:
raise errors.NotFoundError(msg='数据规则不存在')
return data_rule
@staticmethod
async def get_models() -> list[str]:
"""获取所有数据规则可用模型"""
return list(settings.DATA_PERMISSION_MODELS.keys())
@staticmethod
async def get_columns(model: str) -> list[GetDataRuleColumnDetail]:
"""
获取数据规则可用模型的字段列表
:param model: 模型名称
:return:
"""
if model not in settings.DATA_PERMISSION_MODELS:
raise errors.NotFoundError(msg='数据规则可用模型不存在')
model_ins = dynamic_import_data_model(settings.DATA_PERMISSION_MODELS[model])
model_columns = [
GetDataRuleColumnDetail(key=column.key, comment=column.comment)
for column in model_ins.__table__.columns
if column.key not in settings.DATA_PERMISSION_COLUMN_EXCLUDE
]
return model_columns
@staticmethod
async def get_list(*, db: AsyncSession, name: str | None) -> dict[str, Any]:
"""
获取数据规则列表
:param db: 数据库会话
:param name: 规则名称
:return:
"""
data_rule_select = await data_rule_dao.get_select(name=name)
return await paging_data(db, data_rule_select)
@staticmethod
async def get_all(*, db: AsyncSession) -> Sequence[DataRule]:
"""
获取所有数据规则
:param db: 数据库会话
:return:
"""
data_rules = await data_rule_dao.get_all(db)
return data_rules
@staticmethod
async def create(*, db: AsyncSession, obj: CreateDataRuleParam) -> None:
"""
创建数据规则
:param db: 数据库会话
:param obj: 规则创建参数
:return:
"""
data_rule = await data_rule_dao.get_by_name(db, obj.name)
if data_rule:
raise errors.ConflictError(msg='数据规则已存在')
await data_rule_dao.create(db, obj)
@staticmethod
async def update(*, db: AsyncSession, pk: int, obj: UpdateDataRuleParam) -> int:
"""
更新数据规则
:param db: 数据库会话
:param pk: 规则 ID
:param obj: 规则更新参数
:return:
"""
data_rule = await data_rule_dao.get(db, pk)
if not data_rule:
raise errors.NotFoundError(msg='数据规则不存在')
if data_rule.name != obj.name and await data_rule_dao.get_by_name(db, obj.name):
raise errors.ConflictError(msg='数据规则已存在')
count = await data_rule_dao.update(db, pk, obj)
return count
@staticmethod
async def delete(*, db: AsyncSession, obj: DeleteDataRuleParam) -> int:
"""
批量删除数据规则
:param db: 数据库会话
:param obj: 规则 ID 列表
:return:
"""
count = await data_rule_dao.delete(db, obj.pks)
return count
data_rule_service: DataRuleService = DataRuleService()
@@ -0,0 +1,144 @@
from collections.abc import Sequence
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.admin.crud.crud_data_scope import data_scope_dao
from backend.app.admin.model import DataScope
from backend.app.admin.schema.data_scope import (
CreateDataScopeParam,
DeleteDataScopeParam,
UpdateDataScopeParam,
UpdateDataScopeRuleParam,
)
from backend.common.exception import errors
from backend.common.pagination import paging_data
from backend.core.conf import settings
from backend.database.redis import redis_client
class DataScopeService:
"""数据范围服务类"""
@staticmethod
async def get(*, db: AsyncSession, pk: int) -> DataScope:
"""
获取数据范围详情
:param db: 数据库会话
:param pk: 范围 ID
:return:
"""
data_scope = await data_scope_dao.get(db, pk)
if not data_scope:
raise errors.NotFoundError(msg='数据范围不存在')
return data_scope
@staticmethod
async def get_all(*, db: AsyncSession) -> Sequence[DataScope]:
"""
获取所有数据范围
:param db: 数据库会话
:return:
"""
data_scopes = await data_scope_dao.get_all(db)
return data_scopes
@staticmethod
async def get_rules(*, db: AsyncSession, pk: int) -> DataScope:
"""
获取数据范围规则
:param db: 数据库会话
:param pk: 范围 ID
:return:
"""
data_scope = await data_scope_dao.get_with_relation(db, pk)
if not data_scope:
raise errors.NotFoundError(msg='数据范围不存在')
return data_scope
@staticmethod
async def get_list(*, db: AsyncSession, name: str | None, status: int | None) -> dict[str, Any]:
"""
获取数据范围列表
:param db: 数据库会话
:param name: 范围名称
:param status: 范围状态
:return:
"""
data_scope_select = await data_scope_dao.get_select(name, status)
return await paging_data(db, data_scope_select)
@staticmethod
async def create(*, db: AsyncSession, obj: CreateDataScopeParam) -> None:
"""
创建数据范围
:param db: 数据库会话
:param obj: 数据范围参数
:return:
"""
data_scope = await data_scope_dao.get_by_name(db, obj.name)
if data_scope:
raise errors.ConflictError(msg='数据范围已存在')
await data_scope_dao.create(db, obj)
@staticmethod
async def update(*, db: AsyncSession, pk: int, obj: UpdateDataScopeParam) -> int:
"""
更新数据范围
:param db: 数据库会话
:param pk: 范围 ID
:param obj: 数据范围更新参数
:return:
"""
data_scope = await data_scope_dao.get(db, pk)
if not data_scope:
raise errors.NotFoundError(msg='数据范围不存在')
if data_scope.name != obj.name and await data_scope_dao.get_by_name(db, obj.name):
raise errors.ConflictError(msg='数据范围已存在')
count = await data_scope_dao.update(db, pk, obj)
for role in await data_scope.awaitable_attrs.roles:
for user in await role.awaitable_attrs.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def update_data_scope_rule(*, db: AsyncSession, pk: int, rule_ids: UpdateDataScopeRuleParam) -> int:
"""
更新数据范围规则
:param pk: 范围 ID
:param rule_ids: 规则 ID 列表
:return:
"""
count = await data_scope_dao.update_rules(db, pk, rule_ids)
return count
@staticmethod
async def delete(*, db: AsyncSession, obj: DeleteDataScopeParam) -> int:
"""
批量删除数据范围
:param db: 数据库会话
:param obj: 范围 ID 列表
:return:
"""
count = await data_scope_dao.delete(db, obj.pks)
for pk in obj.pks:
data_rule = await data_scope_dao.get(db, pk)
if data_rule:
for role in await data_rule.awaitable_attrs.roles:
for user in await role.awaitable_attrs.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
data_scope_service: DataScopeService = DataScopeService()
+122
View File
@@ -0,0 +1,122 @@
from typing import Any
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.admin.crud.crud_dept import dept_dao
from backend.app.admin.model import Dept
from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
from backend.common.exception import errors
from backend.core.conf import settings
from backend.database.redis import redis_client
from backend.utils.build_tree import get_tree_data
class DeptService:
"""部门服务类"""
@staticmethod
async def get(*, db: AsyncSession, pk: int) -> Dept:
"""
获取部门详情
:param db: 数据库会话
:param pk: 部门 ID
:return:
"""
dept = await dept_dao.get(db, pk)
if not dept:
raise errors.NotFoundError(msg='部门不存在')
return dept
@staticmethod
async def get_tree(
*,
db: AsyncSession,
request: Request,
name: str | None,
leader: str | None,
phone: str | None,
status: int | None,
) -> list[dict[str, Any]]:
"""
获取部门树形结构
:param db: 数据库会话
:param request: FastAPI 请求对象
:param name: 部门名称
:param leader: 部门负责人
:param phone: 联系电话
:param status: 状态
:return:
"""
dept_select = await dept_dao.get_all(request, db, name, leader, phone, status)
tree_data = get_tree_data(dept_select)
return tree_data
@staticmethod
async def create(*, db: AsyncSession, obj: CreateDeptParam) -> None:
"""
创建部门
:param db: 数据库会话
:param obj: 部门创建参数
:return:
"""
dept = await dept_dao.get_by_name(db, obj.name)
if dept:
raise errors.ConflictError(msg='部门名称已存在')
if obj.parent_id:
parent_dept = await dept_dao.get(db, obj.parent_id)
if not parent_dept:
raise errors.NotFoundError(msg='父级部门不存在')
await dept_dao.create(db, obj)
@staticmethod
async def update(*, db: AsyncSession, pk: int, obj: UpdateDeptParam) -> int:
"""
更新部门
:param db: 数据库会话
:param pk: 部门 ID
:param obj: 部门更新参数
:return:
"""
dept = await dept_dao.get(db, pk)
if not dept:
raise errors.NotFoundError(msg='部门不存在')
if dept.name != obj.name and await dept_dao.get_by_name(db, obj.name):
raise errors.ConflictError(msg='部门名称已存在')
if obj.parent_id:
parent_dept = await dept_dao.get(db, obj.parent_id)
if not parent_dept:
raise errors.NotFoundError(msg='父级部门不存在')
if obj.parent_id == dept.id:
raise errors.ForbiddenError(msg='禁止关联自身为父级')
count = await dept_dao.update(db, pk, obj)
return count
@staticmethod
async def delete(*, db: AsyncSession, pk: int) -> int:
"""
删除部门
:param db: 数据库会话
:param pk: 部门 ID
:return:
"""
dept = await dept_dao.get_with_relation(db, pk)
if dept.users:
raise errors.ConflictError(msg='部门下存在用户,无法删除')
children = await dept_dao.get_children(db, pk)
if children:
raise errors.ConflictError(msg='部门下存在子部门,无法删除')
count = await dept_dao.delete(db, pk)
for user in dept.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
dept_service: DeptService = DeptService()
@@ -0,0 +1,89 @@
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
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(
*,
db: AsyncSession,
user_uuid: str,
username: str,
login_time: datetime,
status: int,
msg: str,
) -> None:
"""
创建登录日志
:param db: 数据库会话
: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,
)
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()
+139
View File
@@ -0,0 +1,139 @@
from typing import Any
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.admin.crud.crud_menu import menu_dao
from backend.app.admin.model import Menu
from backend.app.admin.schema.menu import CreateMenuParam, UpdateMenuParam
from backend.common.exception import errors
from backend.core.conf import settings
from backend.database.redis import redis_client
from backend.utils.build_tree import get_tree_data, get_vben5_tree_data
class MenuService:
"""菜单服务类"""
@staticmethod
async def get(*, db: AsyncSession, pk: int) -> Menu:
"""
获取菜单详情
:param db: 数据库会话
:param pk: 菜单 ID
:return:
"""
menu = await menu_dao.get(db, menu_id=pk)
if not menu:
raise errors.NotFoundError(msg='菜单不存在')
return menu
@staticmethod
async def get_tree(*, db: AsyncSession, title: str | None, status: int | None) -> list[dict[str, Any]]:
"""
获取菜单树形结构
:param db: 数据库会话
:param title: 菜单标题
:param status: 状态
:return:
"""
menu_data = await menu_dao.get_all(db, title=title, status=status)
menu_tree = get_tree_data(menu_data)
return menu_tree
@staticmethod
async def get_sidebar(*, db: AsyncSession, request: Request) -> list[dict[str, Any] | None]:
"""
获取用户的菜单侧边栏
:param db: 数据库会话
:param request: FastAPI 请求对象
:return:
"""
if request.user.is_superuser:
menu_data = await menu_dao.get_sidebar(db, None)
else:
roles = request.user.roles
menu_ids = set()
if roles:
for role in roles:
menu_ids.update(menu.id for menu in role.menus)
menu_data = await menu_dao.get_sidebar(db, list(menu_ids))
menu_tree = get_vben5_tree_data(menu_data)
return menu_tree
@staticmethod
async def create(*, db: AsyncSession, obj: CreateMenuParam) -> None:
"""
创建菜单
:param db: 数据库会话
:param obj: 菜单创建参数
:return:
"""
title = await menu_dao.get_by_title(db, obj.title)
if title:
raise errors.ConflictError(msg='菜单标题已存在')
if obj.parent_id:
parent_menu = await menu_dao.get(db, obj.parent_id)
if not parent_menu:
raise errors.NotFoundError(msg='父级菜单不存在')
await menu_dao.create(db, obj)
@staticmethod
async def update(*, db: AsyncSession, pk: int, obj: UpdateMenuParam) -> int:
"""
更新菜单
:param db: 数据库会话
:param pk: 菜单 ID
:param obj: 菜单更新参数
:return:
"""
menu = await menu_dao.get(db, pk)
if not menu:
raise errors.NotFoundError(msg='菜单不存在')
if menu.title != obj.title and await menu_dao.get_by_title(db, obj.title):
raise errors.ConflictError(msg='菜单标题已存在')
if obj.parent_id:
parent_menu = await menu_dao.get(db, obj.parent_id)
if not parent_menu:
raise errors.NotFoundError(msg='父级菜单不存在')
if obj.parent_id == menu.id:
raise errors.ForbiddenError(msg='禁止关联自身为父级')
count = await menu_dao.update(db, pk, obj)
for role in await menu.awaitable_attrs.roles:
for user in await role.awaitable_attrs.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def delete(*, db: AsyncSession, pk: int) -> int:
"""
删除菜单
:param db: 数据库会话
:param pk: 菜单 ID
:return:
"""
children = await menu_dao.get_children(db, pk)
if children:
raise errors.ConflictError(msg='菜单下存在子菜单,无法删除')
menu = await menu_dao.get(db, pk)
count = await menu_dao.delete(db, pk)
if menu:
for role in await menu.awaitable_attrs.roles:
for user in await role.awaitable_attrs.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
menu_service: MenuService = MenuService()
@@ -0,0 +1,72 @@
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()
+123
View File
@@ -0,0 +1,123 @@
import io
import json
import os
import shutil
import zipfile
from typing import Any
import anyio
from fastapi import UploadFile
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.tools import uninstall_requirements_async
from backend.utils.file_ops import install_git_plugin, install_zip_plugin
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 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:
"""
plugin_dir = anyio.Path(PLUGIN_DIR / plugin)
if not await plugin_dir.exists():
raise errors.NotFoundError(msg='插件不存在')
await uninstall_requirements_async(plugin)
bacup_dir = PLUGIN_DIR / f'{plugin}.{timezone.now().strftime("%Y%m%d%H%M%S")}.backup'
shutil.move(plugin_dir, bacup_dir)
await redis_client.delete(f'{settings.PLUGIN_REDIS_PREFIX}:{plugin}')
await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'ture')
@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()
with zipfile.ZipFile(bio, 'w') as zf:
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) # noqa: ASYNC240
zf.write(file_path, os.path.join(plugin, arcname))
bio.seek(0)
return bio
plugin_service: PluginService = PluginService()
+200
View File
@@ -0,0 +1,200 @@
from collections.abc import Sequence
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.admin.crud.crud_data_scope import data_scope_dao
from backend.app.admin.crud.crud_menu import menu_dao
from backend.app.admin.crud.crud_role import role_dao
from backend.app.admin.model import Role
from backend.app.admin.schema.role import (
CreateRoleParam,
DeleteRoleParam,
UpdateRoleMenuParam,
UpdateRoleParam,
UpdateRoleScopeParam,
)
from backend.common.exception import errors
from backend.common.pagination import paging_data
from backend.core.conf import settings
from backend.database.redis import redis_client
from backend.utils.build_tree import get_tree_data
class RoleService:
"""角色服务类"""
@staticmethod
async def get(*, db: AsyncSession, pk: int) -> Role:
"""
获取角色详情
:param db: 数据库会话
:param pk: 角色 ID
:return:
"""
role = await role_dao.get_with_relation(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
return role
@staticmethod
async def get_all(*, db: AsyncSession) -> Sequence[Role]:
"""
获取所有角色
:param db: 数据库会话
:return:
"""
roles = await role_dao.get_all(db)
return roles
@staticmethod
async def get_list(*, db: AsyncSession, name: str | None, status: int | None) -> dict[str, Any]:
"""
获取角色列表
:param db: 数据库会话
:param name: 角色名称
:param status: 状态
:return:
"""
role_select = await role_dao.get_select(name=name, status=status)
return await paging_data(db, role_select)
@staticmethod
async def get_menu_tree(*, db: AsyncSession, pk: int) -> list[dict[str, Any] | None]:
"""
获取角色的菜单树形结构
:param db: 数据库会话
:param pk: 角色 ID
:return:
"""
role = await role_dao.get_with_relation(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
menu_tree = get_tree_data(role.menus) if role.menus else []
return menu_tree
@staticmethod
async def get_scopes(*, db: AsyncSession, pk: int) -> list[int]:
"""
获取角色数据范围列表
:param db: 数据库会话
:param pk:
:return:
"""
role = await role_dao.get_with_relation(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
scope_ids = [scope.id for scope in role.scopes]
return scope_ids
@staticmethod
async def create(*, db: AsyncSession, obj: CreateRoleParam) -> None:
"""
创建角色
:param db: 数据库会话
:param obj: 角色创建参数
:return:
"""
role = await role_dao.get_by_name(db, obj.name)
if role:
raise errors.ConflictError(msg='角色已存在')
await role_dao.create(db, obj)
@staticmethod
async def update(*, db: AsyncSession, pk: int, obj: UpdateRoleParam) -> int:
"""
更新角色
:param db: 数据库会话
:param pk: 角色 ID
:param obj: 角色更新参数
:return:
"""
role = await role_dao.get(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
if role.name != obj.name and await role_dao.get_by_name(db, obj.name):
raise errors.ConflictError(msg='角色已存在')
count = await role_dao.update(db, pk, obj)
for user in await role.awaitable_attrs.users:
await redis_client.delete_prefix(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def update_role_menu(*, db: AsyncSession, pk: int, menu_ids: UpdateRoleMenuParam) -> int:
"""
更新角色菜单
:param db: 数据库会话
:param pk: 角色 ID
:param menu_ids: 菜单 ID 列表
:return:
"""
role = await role_dao.get(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
for menu_id in menu_ids.menus:
menu = await menu_dao.get(db, menu_id)
if not menu:
raise errors.NotFoundError(msg='菜单不存在')
count = await role_dao.update_menus(db, pk, menu_ids)
for user in await role.awaitable_attrs.users:
await redis_client.delete_prefix(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def update_role_scope(*, db: AsyncSession, pk: int, scope_ids: UpdateRoleScopeParam) -> int:
"""
更新角色数据范围
:param db: 数据库会话
:param pk: 角色 ID
:param scope_ids: 权限规则 ID 列表
:return:
"""
role = await role_dao.get(db, pk)
if not role:
raise errors.NotFoundError(msg='角色不存在')
for scope_id in scope_ids.scopes:
scope = await data_scope_dao.get(db, scope_id)
if not scope:
raise errors.NotFoundError(msg='数据范围不存在')
count = await role_dao.update_scopes(db, pk, scope_ids)
for user in await role.awaitable_attrs.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def delete(*, db: AsyncSession, obj: DeleteRoleParam) -> int:
"""
批量删除角色
:param db: 数据库会话
:param obj: 角色 ID 列表
:return:
"""
count = await role_dao.delete(db, obj.pks)
for pk in obj.pks:
role = await role_dao.get(db, pk)
if role:
for user in await role.awaitable_attrs.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
role_service: RoleService = RoleService()
+317
View File
@@ -0,0 +1,317 @@
import random
from collections.abc import Sequence
from typing import Any
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.admin.crud.crud_dept import dept_dao
from backend.app.admin.crud.crud_role import role_dao
from backend.app.admin.crud.crud_user import user_dao
from backend.app.admin.model import Role, User
from backend.app.admin.schema.user import (
AddUserParam,
ResetPasswordParam,
UpdateUserParam,
)
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, password_verify
from backend.core.conf import settings
from backend.database.redis import redis_client
class UserService:
"""用户服务类"""
@staticmethod
async def get_userinfo(*, db: AsyncSession, pk: int | None = None, username: str | None = None) -> User:
"""
获取用户信息
:param db: 数据库会话
:param pk: 用户 ID
:param username: 用户名
:return:
"""
user = await user_dao.get_with_relation(db, user_id=pk, username=username)
if not user:
raise errors.NotFoundError(msg='用户不存在')
return user
@staticmethod
async def get_roles(*, db: AsyncSession, pk: int) -> Sequence[Role]:
"""
获取用户所有角色
:param db: 数据库会话
:param pk: 用户 ID
:return:
"""
user = await user_dao.get_with_relation(db, user_id=pk)
if not user:
raise errors.NotFoundError(msg='用户不存在')
return user.roles
@staticmethod
async def get_list(*, db: AsyncSession, dept: int, username: str, phone: str, status: int) -> dict[str, Any]:
"""
获取用户列表
:param db: 数据库会话
:param dept: 部门 ID
:param username: 用户名
:param phone: 手机号
:param status: 状态
:return:
"""
user_select = await user_dao.get_select(dept=dept, username=username, phone=phone, status=status)
return await paging_data(db, user_select)
@staticmethod
async def create(*, db: AsyncSession, obj: AddUserParam) -> None:
"""
创建用户
:param db: 数据库会话
:param obj: 用户添加参数
:return:
"""
if await user_dao.get_by_username(db, obj.username):
raise errors.ConflictError(msg='用户名已注册')
obj.nickname = obj.nickname or f'#{random.randrange(88888, 99999)}'
if not obj.password:
raise errors.RequestError(msg='密码不允许为空')
if not await dept_dao.get(db, obj.dept_id):
raise errors.NotFoundError(msg='部门不存在')
for role_id in obj.roles:
if not await role_dao.get(db, role_id):
raise errors.NotFoundError(msg='角色不存在')
await user_dao.add(db, obj)
@staticmethod
async def update(*, db: AsyncSession, pk: int, obj: UpdateUserParam) -> int:
"""
更新用户信息
:param db: 数据库会话
:param pk: 用户 ID
:param obj: 用户更新参数
:return:
"""
user = await user_dao.get_with_relation(db, user_id=pk)
if not user:
raise errors.NotFoundError(msg='用户不存在')
if obj.username != user.username and await user_dao.get_by_username(db, obj.username):
raise errors.ConflictError(msg='用户名已注册')
for role_id in obj.roles:
if not await role_dao.get(db, role_id):
raise errors.NotFoundError(msg='角色不存在')
count = await user_dao.update(db, user, obj)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def update_permission(*, db: AsyncSession, request: Request, pk: int, type: UserPermissionType) -> int: # noqa: C901
"""
更新用户权限
:param db: 数据库会话
:param request: FastAPI 请求对象
:param pk: 用户 ID
:param type: 权限类型
:return:
"""
match type:
case UserPermissionType.superuser:
user = await user_dao.get(db, pk)
if not user:
raise errors.NotFoundError(msg='用户不存在')
if pk == request.user.id:
raise errors.ForbiddenError(msg='禁止修改自身权限')
count = await user_dao.set_super(db, pk, is_super=not user.status)
case UserPermissionType.staff:
user = await user_dao.get(db, pk)
if not user:
raise errors.NotFoundError(msg='用户不存在')
if pk == request.user.id:
raise errors.ForbiddenError(msg='禁止修改自身权限')
count = await user_dao.set_staff(db, pk, is_staff=not user.is_staff)
case UserPermissionType.status:
user = await user_dao.get(db, pk)
if not user:
raise errors.NotFoundError(msg='用户不存在')
if pk == request.user.id:
raise errors.ForbiddenError(msg='禁止修改自身权限')
count = await user_dao.set_status(db, pk, 0 if user.status == 1 else 1)
case UserPermissionType.multi_login:
user = await user_dao.get(db, pk)
if not user:
raise errors.NotFoundError(msg='用户不存在')
multi_login = user.is_multi_login if pk != user.id else request.user.is_multi_login
new_multi_login = not multi_login
count = await user_dao.set_multi_login(db, pk, multi_login=new_multi_login)
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(
key_prefix,
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)
case _:
raise errors.RequestError(msg='权限类型不存在')
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def reset_password(*, db: AsyncSession, pk: int, password: str) -> int:
"""
重置用户密码
:param db: 数据库会话
:param pk: 用户 ID
:param password: 新密码
:return:
"""
user = await user_dao.get(db, pk)
if not user:
raise errors.NotFoundError(msg='用户不存在')
count = await user_dao.reset_password(db, user.id, password)
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 prefix in key_prefix:
await redis_client.delete(prefix)
return count
@staticmethod
async def update_nickname(*, db: AsyncSession, request: Request, nickname: str) -> int:
"""
更新当前用户昵称
:param db: 数据库会话
:param request: FastAPI 请求对象
:param nickname: 用户昵称
:return:
"""
token = get_token(request)
token_payload = jwt_decode(token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
count = await user_dao.update_nickname(db, token_payload.id, nickname)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def update_avatar(*, db: AsyncSession, request: Request, avatar: str) -> int:
"""
更新当前用户头像
:param db: 数据库会话
:param request: FastAPI 请求对象
:param avatar: 头像地址
:return:
"""
token = get_token(request)
token_payload = jwt_decode(token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
count = await user_dao.update_avatar(db, token_payload.id, avatar)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def update_email(*, db: AsyncSession, request: Request, captcha: str, email: str) -> int:
"""
更新当前用户邮箱
:param db: 数据库会话
:param request: FastAPI 请求对象
:param captcha: 邮箱验证码
:param email: 邮箱
:return:
"""
token = get_token(request)
token_payload = jwt_decode(token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
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, token_payload.id, email)
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
return count
@staticmethod
async def update_password(*, db: AsyncSession, request: Request, obj: ResetPasswordParam) -> int:
"""
更新当前用户密码
:param db: 数据库会话
:param request: FastAPI 请求对象
:param obj: 密码重置参数
:return:
"""
token = get_token(request)
token_payload = jwt_decode(token)
user = await user_dao.get(db, token_payload.id)
if not user:
raise errors.NotFoundError(msg='用户不存在')
if not password_verify(obj.old_password, user.password):
raise errors.RequestError(msg='原密码错误')
if obj.new_password != obj.confirm_password:
raise errors.RequestError(msg='密码输入不一致')
count = await user_dao.reset_password(db, user.id, obj.new_password)
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 prefix in key_prefix:
await redis_client.delete_prefix(prefix)
return count
@staticmethod
async def delete(*, db: AsyncSession, pk: int) -> int:
"""
删除用户
:param db: 数据库会话
:param pk: 用户 ID
:return:
"""
user = await user_dao.get(db, pk)
if not user:
raise errors.NotFoundError(msg='用户不存在')
count = await user_dao.delete(db, user.id)
key_prefix = [
f'{settings.TOKEN_REDIS_PREFIX}:{user.id}',
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}',
]
for key in key_prefix:
await redis_client.delete_prefix(key)
return count
user_service: UserService = UserService()
View File
@@ -0,0 +1,7 @@
from starlette.testclient import TestClient
def test_logout(client: TestClient, token_headers: dict[str, str]) -> None:
response = client.post('/auth/logout', headers=token_headers)
assert response.status_code == 200
assert response.json()['code'] == 200
+39
View File
@@ -0,0 +1,39 @@
from collections.abc import Generator
import pytest
from starlette.testclient import TestClient
from backend.app.admin.tests.utils.db import override_get_db
from backend.core.conf import settings
from backend.database.db import get_db
from backend.main import app
# 重载数据库
app.dependency_overrides[get_db] = override_get_db
# Test data
PYTEST_USERNAME = 'admin'
PYTEST_PASSWORD = '123456'
PYTEST_BASE_URL = f'http://testserver{settings.FASTAPI_API_V1_PATH}'
@pytest.fixture(scope='module')
def client() -> Generator:
with TestClient(app, base_url=PYTEST_BASE_URL) as c:
yield c
@pytest.fixture(scope='module')
def token_headers(client: TestClient) -> dict[str, str]:
params = {
'username': PYTEST_USERNAME,
'password': PYTEST_PASSWORD,
}
response = client.post('/auth/login/swagger', params=params)
response.raise_for_status()
token_type = response.json()['token_type']
access_token = response.json()['access_token']
headers = {'Authorization': f'{token_type} {access_token}'}
return headers
+15
View File
@@ -0,0 +1,15 @@
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio.session import AsyncSession
from backend.database.db import create_async_engine_and_session, create_database_url
TEST_SQLALCHEMY_DATABASE_URL = create_database_url(unittest=True)
_, async_test_db_session = create_async_engine_and_session(TEST_SQLALCHEMY_DATABASE_URL)
async def override_get_db() -> AsyncGenerator[AsyncSession, None]:
"""session 生成器"""
async with async_test_db_session() as session:
yield session
-1
View File
@@ -1 +0,0 @@
Generic single-database configuration with an async dbapi.
-105
View File
@@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
import asyncio
import os
import sys
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config, pool
from sqlalchemy.ext.asyncio import AsyncEngine
sys.path.append('../../')
from backend.app.core import path_conf # noqa: E402
if not os.path.exists(path_conf.Versions):
os.makedirs(path_conf.Versions)
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
from backend.app.models import MappedBase # noqa: E402
target_metadata = MappedBase.metadata
# other values from the config, defined by the needs of env.py,
from backend.app.database.db_mysql import SQLALCHEMY_DATABASE_URL # noqa: E402
config.set_main_option('sqlalchemy.url', SQLALCHEMY_DATABASE_URL)
def include_name(name, type_, parent_names):
if type_ == 'table':
return name in target_metadata.tables
else:
return True
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option('sqlalchemy.url')
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={'paramstyle': 'named'},
include_name=include_name,
)
with context.begin_transaction():
context.run_migrations()
def do_run_migrations(connection):
context.configure(
connection=connection,
target_metadata=target_metadata,
include_name=include_name,
)
with context.begin_transaction():
context.run_migrations()
async def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = AsyncEngine(
engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
future=True,
)
)
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
if context.is_offline_mode():
run_migrations_offline()
else:
asyncio.run(run_migrations_online())
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-34
View File
@@ -1,34 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from backend.app.api.v1.api import router as api_router
from backend.app.api.v1.auth import router as auth_router
from backend.app.api.v1.casbin import router as casbin_router
from backend.app.api.v1.dept import router as dept_router
from backend.app.api.v1.dict_data import router as dict_data_router
from backend.app.api.v1.dict_type import router as dict_type_router
from backend.app.api.v1.log import router as log_router
from backend.app.api.v1.menu import router as menu_router
from backend.app.api.v1.mixed import router as mixed_router
from backend.app.api.v1.monitor import router as monitor_router
from backend.app.api.v1.role import router as role_router
from backend.app.api.v1.task import router as task_router
from backend.app.api.v1.user import router as user_router
from backend.app.core.conf import settings
v1 = APIRouter(prefix=settings.API_V1_STR)
v1.include_router(auth_router, prefix='/auth', tags=['认证'])
v1.include_router(user_router, prefix='/users', tags=['用户管理'])
v1.include_router(casbin_router, prefix='/casbin', tags=['权限管理'])
v1.include_router(dept_router, prefix='/depts', tags=['部门管理'])
v1.include_router(role_router, prefix='/roles', tags=['角色管理'])
v1.include_router(menu_router, prefix='/menus', tags=['菜单管理'])
v1.include_router(api_router, prefix='/apis', tags=['API管理'])
v1.include_router(dict_type_router, prefix='/dict-types', tags=['字典类型管理'])
v1.include_router(dict_data_router, prefix='/dict-datas', tags=['字典数据管理'])
v1.include_router(log_router, prefix='/logs', tags=['日志管理'])
v1.include_router(monitor_router, prefix='/monitors', tags=['监控管理'])
v1.include_router(task_router, prefix='/tasks', tags=['任务管理'])
v1.include_router(mixed_router, prefix='/mixes', tags=['杂项'])
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-61
View File
@@ -1,61 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Annotated
from fastapi import APIRouter, Query
from backend.app.common.jwt import DependsJwtAuth
from backend.app.common.pagination import PageDepends, paging_data
from backend.app.common.rbac import DependsRBAC
from backend.app.common.response.response_schema import response_base
from backend.app.database.db_mysql import CurrentSession
from backend.app.schemas.api import CreateApi, GetAllApi, UpdateApi
from backend.app.services.api_service import ApiService
router = APIRouter()
@router.get('/all', summary='获取所有接口', dependencies=[DependsJwtAuth])
async def get_all_apis():
data = await ApiService.get_all()
return await response_base.success(data=data)
@router.get('/{pk}', summary='获取接口详情', dependencies=[DependsJwtAuth])
async def get_api(pk: int):
api = await ApiService.get(pk=pk)
return await response_base.success(data=api)
@router.get('', summary='(模糊条件)分页获取所有接口', dependencies=[DependsJwtAuth, PageDepends])
async def get_api_list(
db: CurrentSession,
name: Annotated[str | None, Query()] = None,
method: Annotated[str | None, Query()] = None,
path: Annotated[str | None, Query()] = None,
):
api_select = await ApiService.get_select(name=name, method=method, path=path)
page_data = await paging_data(db, api_select, GetAllApi)
return await response_base.success(data=page_data)
@router.post('', summary='创建接口', dependencies=[DependsRBAC])
async def create_api(obj: CreateApi):
await ApiService.create(obj=obj)
return await response_base.success()
@router.put('/{pk}', summary='更新接口', dependencies=[DependsRBAC])
async def update_api(pk: int, obj: UpdateApi):
count = await ApiService.update(pk=pk, obj=obj)
if count > 0:
return await response_base.success()
return await response_base.fail()
@router.delete('', summary='(批量)删除接口', dependencies=[DependsRBAC])
async def delete_api(pk: Annotated[list[int], Query(...)]):
count = await ApiService.delete(pk=pk)
if count > 0:
return await response_base.success()
return await response_base.fail()
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from backend.app.api.v1.auth.auth import router as auth_router
from backend.app.api.v1.auth.captcha import router as captcha_router
router = APIRouter()
router.include_router(auth_router)
router.include_router(captcha_router)
-65
View File
@@ -1,65 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Annotated
from fastapi import APIRouter, Depends, Query, Request
from fastapi.security import OAuth2PasswordRequestForm
from fastapi_limiter.depends import RateLimiter
from starlette.background import BackgroundTasks
from backend.app.common.jwt import DependsJwtAuth
from backend.app.common.response.response_schema import response_base
from backend.app.schemas.token import GetLoginToken, GetNewToken, GetSwaggerToken
from backend.app.schemas.user import AuthLogin
from backend.app.services.auth_service import AuthService
router = APIRouter()
@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> GetSwaggerToken:
token, user = await AuthService().swagger_login(form_data=form_data)
return GetSwaggerToken(access_token=token, user=user) # type: ignore
@router.post(
'/login',
summary='用户登录',
description='json 格式登录, 仅支持在第三方api工具调试接口, 例如: postman',
dependencies=[Depends(RateLimiter(times=5, minutes=1))],
)
async def user_login(request: Request, obj: AuthLogin, background_tasks: BackgroundTasks):
access_token, refresh_token, access_expire, refresh_expire, user = await AuthService().login(
request=request, obj=obj, background_tasks=background_tasks
)
data = GetLoginToken(
access_token=access_token,
refresh_token=refresh_token,
access_token_expire_time=access_expire,
refresh_token_expire_time=refresh_expire,
user=user, # type: ignore
)
return await response_base.success(data=data)
@router.post('/new_token', summary='创建新 token', dependencies=[DependsJwtAuth])
async def create_new_token(request: Request, refresh_token: Annotated[str, Query(...)]):
(
new_access_token,
new_refresh_token,
new_access_token_expire_time,
new_refresh_token_expire_time,
) = await AuthService.new_token(request=request, refresh_token=refresh_token)
data = GetNewToken(
access_token=new_access_token,
access_token_expire_time=new_access_token_expire_time,
refresh_token=new_refresh_token,
refresh_token_expire_time=new_refresh_token_expire_time,
)
return await response_base.success(data=data)
@router.post('/logout', summary='用户登出', dependencies=[DependsJwtAuth])
async def user_logout(request: Request):
await AuthService.logout(request=request)
return await response_base.success()

Some files were not shown because too many files have changed in this diff Show More