├── .env ├── .gitignore ├── .idea └── vue-element-admin-fastapi.iml ├── LICENSE ├── README.md ├── backend ├── .env ├── .gitignore └── app │ ├── .flake8 │ ├── .gitignore │ ├── README │ ├── alembic.ini │ ├── alembic │ ├── README │ ├── env.py │ ├── script.py.mako │ └── versions │ │ └── .keep │ ├── app │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── api_v1 │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ ├── endpoints │ │ │ │ ├── __init__.py │ │ │ │ ├── login.py │ │ │ │ ├── role.py │ │ │ │ └── utils.py │ │ │ ├── report │ │ │ │ ├── __init__.py │ │ │ │ ├── gen_excel.py │ │ │ │ ├── gen_report.py │ │ │ │ └── report │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── system_user.py │ │ │ ├── system │ │ │ │ ├── __init__.py │ │ │ │ ├── department.py │ │ │ │ ├── dict.py │ │ │ │ ├── menu.py │ │ │ │ └── user.py │ │ │ └── websocket │ │ │ │ ├── __init__.py │ │ │ │ ├── demo.py │ │ │ │ └── server.py │ │ └── deps.py │ ├── celery_app │ │ ├── __init__.py │ │ ├── celery_app.py │ │ └── worker │ │ │ ├── __init__.py │ │ │ └── example.py │ ├── core │ │ ├── __init__.py │ │ ├── config.py │ │ └── security.py │ ├── crud │ │ ├── __init__.py │ │ ├── base.py │ │ ├── crud_menu.py │ │ └── crud_user.py │ ├── db │ │ ├── __init__.py │ │ ├── base.py │ │ ├── base_class.py │ │ ├── export_data.py │ │ ├── init_data │ │ │ ├── department.csv │ │ │ ├── dict_data.csv │ │ │ ├── dict_type.csv │ │ │ ├── menu.csv │ │ │ ├── role.csv │ │ │ ├── role_menu.csv │ │ │ ├── user.csv │ │ │ ├── user_department.csv │ │ │ ├── user_dict.csv │ │ │ └── user_role.csv │ │ ├── init_db.py │ │ └── session.py │ ├── db_pre_start │ │ ├── backend_pre_start.py │ │ └── tests_pre_start.py │ ├── email-templates │ │ ├── build │ │ │ ├── new_account.html │ │ │ ├── reset_password.html │ │ │ └── test_email.html │ │ └── src │ │ │ ├── new_account.mjml │ │ │ ├── reset_password.mjml │ │ │ └── test_email.mjml │ ├── extensions │ │ ├── __init__.py │ │ ├── exception.py │ │ ├── logger.py │ │ ├── request_id.py │ │ └── utils.py │ ├── initial_data.py │ ├── main.py │ ├── middleware │ │ ├── __init__.py │ │ └── access_middle.py │ ├── models │ │ ├── __init__.py │ │ ├── role.py │ │ ├── system.py │ │ └── user.py │ ├── schemas │ │ ├── __init__.py │ │ ├── msg.py │ │ ├── reponse.py │ │ ├── role.py │ │ ├── system │ │ │ ├── __init__.py │ │ │ ├── department.py │ │ │ ├── dict.py │ │ │ └── menu.py │ │ ├── token.py │ │ └── user.py │ └── tests │ │ ├── .gitignore │ │ ├── __init__.py │ │ ├── api │ │ ├── __init__.py │ │ └── api_v1 │ │ │ ├── __init__.py │ │ │ ├── test_celery.py │ │ │ ├── test_login.py │ │ │ └── test_users.py │ │ ├── conftest.py │ │ ├── crud │ │ ├── __init__.py │ │ └── test_user.py │ │ └── utils │ │ ├── __init__.py │ │ ├── user.py │ │ └── utils.py │ ├── main-start.sh │ ├── mypy.ini │ ├── poetry.lock │ ├── prestart.sh │ ├── pyproject.toml │ ├── requirements.txt │ ├── scripts │ ├── format-imports.sh │ ├── format.sh │ ├── lint.sh │ ├── test-cov-html.sh │ └── test.sh │ ├── tests-start.sh │ └── worker-start.sh ├── frontend ├── .editorconfig ├── .env.development ├── .env.production ├── .env.staging ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.es.md ├── README.ja.md ├── README.md ├── README.zh-CN.md ├── babel.config.js ├── build │ └── index.js ├── jest.config.js ├── jsconfig.json ├── mock │ ├── article.js │ ├── index.js │ ├── mock-server.js │ ├── remote-search.js │ ├── role │ │ ├── index.js │ │ └── routes.js │ ├── user.js │ └── utils.js ├── package.json ├── plop-templates │ ├── component │ │ ├── index.hbs │ │ └── prompt.js │ ├── store │ │ ├── index.hbs │ │ └── prompt.js │ ├── utils.js │ └── view │ │ ├── index.hbs │ │ └── prompt.js ├── plopfile.js ├── postcss.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── api │ │ ├── qiniu.js │ │ ├── remote-search.js │ │ ├── role.js │ │ ├── system │ │ │ ├── dept.js │ │ │ ├── dict │ │ │ │ ├── data.js │ │ │ │ └── type.js │ │ │ ├── menu.js │ │ │ ├── post.js │ │ │ ├── role.js │ │ │ └── user.js │ │ └── user.js │ ├── assets │ │ ├── 401_images │ │ │ └── 401.gif │ │ ├── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ │ └── custom-theme │ │ │ ├── fonts │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ │ │ └── index.css │ ├── components │ │ ├── BackToTop │ │ │ └── index.vue │ │ ├── Breadcrumb │ │ │ └── index.vue │ │ ├── Charts │ │ │ ├── Keyboard.vue │ │ │ ├── LineMarker.vue │ │ │ ├── MixChart.vue │ │ │ └── mixins │ │ │ │ └── resize.js │ │ ├── DndList │ │ │ └── index.vue │ │ ├── DragSelect │ │ │ └── index.vue │ │ ├── Dropzone │ │ │ └── index.vue │ │ ├── ErrorLog │ │ │ └── index.vue │ │ ├── Hamburger │ │ │ └── index.vue │ │ ├── HeaderSearch │ │ │ └── index.vue │ │ ├── IconSelect │ │ │ ├── index.vue │ │ │ └── requireIcons.js │ │ ├── ImageCropper │ │ │ ├── index.vue │ │ │ └── utils │ │ │ │ ├── data2blob.js │ │ │ │ ├── effectRipple.js │ │ │ │ ├── language.js │ │ │ │ └── mimes.js │ │ ├── JsonEditor │ │ │ └── index.vue │ │ ├── Kanban │ │ │ └── index.vue │ │ ├── MDinput │ │ │ └── index.vue │ │ ├── MarkdownEditor │ │ │ ├── default-options.js │ │ │ └── index.vue │ │ ├── Pagination │ │ │ └── index.vue │ │ ├── PanThumb │ │ │ └── index.vue │ │ ├── README.md │ │ ├── RightPanel │ │ │ └── index.vue │ │ ├── Screenfull │ │ │ └── index.vue │ │ ├── Share │ │ │ └── DropdownMenu.vue │ │ ├── SizeSelect │ │ │ └── index.vue │ │ ├── Sticky │ │ │ └── index.vue │ │ ├── SvgIcon │ │ │ └── index.vue │ │ ├── TextHoverEffect │ │ │ └── Mallki.vue │ │ ├── ThemePicker │ │ │ └── index.vue │ │ ├── Tinymce │ │ │ ├── components │ │ │ │ └── EditorImage.vue │ │ │ ├── dynamicLoadScript.js │ │ │ ├── index.vue │ │ │ ├── plugins.js │ │ │ └── toolbar.js │ │ ├── Upload │ │ │ ├── SingleImage.vue │ │ │ ├── SingleImage2.vue │ │ │ └── SingleImage3.vue │ │ └── UploadExcel │ │ │ └── index.vue │ ├── directive │ │ ├── button_permission │ │ │ ├── hasPermi.js │ │ │ ├── hasRole.js │ │ │ └── index.js │ │ ├── clipboard │ │ │ ├── clipboard.js │ │ │ └── index.js │ │ ├── el-drag-dialog │ │ │ ├── drag.js │ │ │ └── index.js │ │ ├── el-table │ │ │ ├── adaptive.js │ │ │ └── index.js │ │ ├── permission │ │ │ ├── index.js │ │ │ └── permission.js │ │ ├── sticky.js │ │ └── waves │ │ │ ├── index.js │ │ │ ├── waves.css │ │ │ └── waves.js │ ├── filters │ │ └── index.js │ ├── icons │ │ ├── index.js │ │ ├── svg │ │ │ ├── 404.svg │ │ │ ├── bug.svg │ │ │ ├── build.svg │ │ │ ├── cascader.svg │ │ │ ├── chart.svg │ │ │ ├── checkbox.svg │ │ │ ├── clipboard.svg │ │ │ ├── code.svg │ │ │ ├── color.svg │ │ │ ├── component.svg │ │ │ ├── dashboard.svg │ │ │ ├── date-range.svg │ │ │ ├── date.svg │ │ │ ├── dict.svg │ │ │ ├── documentation.svg │ │ │ ├── download.svg │ │ │ ├── drag.svg │ │ │ ├── druid.svg │ │ │ ├── edit.svg │ │ │ ├── education.svg │ │ │ ├── email.svg │ │ │ ├── example.svg │ │ │ ├── excel.svg │ │ │ ├── exit-fullscreen.svg │ │ │ ├── eye-open.svg │ │ │ ├── eye.svg │ │ │ ├── form.svg │ │ │ ├── fullscreen.svg │ │ │ ├── github.svg │ │ │ ├── guide.svg │ │ │ ├── icon.svg │ │ │ ├── input.svg │ │ │ ├── international.svg │ │ │ ├── job.svg │ │ │ ├── language.svg │ │ │ ├── link.svg │ │ │ ├── list.svg │ │ │ ├── lock.svg │ │ │ ├── log.svg │ │ │ ├── logininfor.svg │ │ │ ├── message.svg │ │ │ ├── money.svg │ │ │ ├── monitor.svg │ │ │ ├── nested.svg │ │ │ ├── number.svg │ │ │ ├── online.svg │ │ │ ├── password.svg │ │ │ ├── pdf.svg │ │ │ ├── people.svg │ │ │ ├── peoples.svg │ │ │ ├── phone.svg │ │ │ ├── post.svg │ │ │ ├── qq.svg │ │ │ ├── question.svg │ │ │ ├── radio.svg │ │ │ ├── rate.svg │ │ │ ├── row.svg │ │ │ ├── search.svg │ │ │ ├── select.svg │ │ │ ├── server.svg │ │ │ ├── shopping.svg │ │ │ ├── size.svg │ │ │ ├── skill.svg │ │ │ ├── slider.svg │ │ │ ├── star.svg │ │ │ ├── swagger.svg │ │ │ ├── switch.svg │ │ │ ├── system.svg │ │ │ ├── tab.svg │ │ │ ├── table.svg │ │ │ ├── textarea.svg │ │ │ ├── theme.svg │ │ │ ├── time-range.svg │ │ │ ├── time.svg │ │ │ ├── tool.svg │ │ │ ├── tree-table.svg │ │ │ ├── tree.svg │ │ │ ├── upload.svg │ │ │ ├── user.svg │ │ │ ├── validCode.svg │ │ │ ├── wechat.svg │ │ │ └── zip.svg │ │ └── svgo.yml │ ├── layout │ │ ├── components │ │ │ ├── AppMain.vue │ │ │ ├── Navbar.vue │ │ │ ├── Settings │ │ │ │ └── index.vue │ │ │ ├── Sidebar │ │ │ │ ├── FixiOSBug.js │ │ │ │ ├── Item.vue │ │ │ │ ├── Link.vue │ │ │ │ ├── Logo.vue │ │ │ │ ├── SidebarItem.vue │ │ │ │ └── index.vue │ │ │ ├── TagsView │ │ │ │ ├── ScrollPane.vue │ │ │ │ └── index.vue │ │ │ └── index.js │ │ ├── index.vue │ │ └── mixin │ │ │ └── ResizeHandler.js │ ├── main.js │ ├── permission.js │ ├── router │ │ └── index.js │ ├── settings.js │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ └── modules │ │ │ ├── app.js │ │ │ ├── errorLog.js │ │ │ ├── permission.js │ │ │ ├── settings.js │ │ │ ├── tagsView.js │ │ │ └── user.js │ ├── styles │ │ ├── btn.scss │ │ ├── element-ui.scss │ │ ├── element-variables.scss │ │ ├── index.scss │ │ ├── mixin.scss │ │ ├── ruoyi.scss │ │ ├── sidebar.scss │ │ ├── transition.scss │ │ └── variables.scss │ ├── utils │ │ ├── auth.js │ │ ├── clipboard.js │ │ ├── error-log.js │ │ ├── get-page-title.js │ │ ├── index.js │ │ ├── open-window.js │ │ ├── permission.js │ │ ├── request.js │ │ ├── ruoyi.js │ │ ├── scroll-to.js │ │ └── validate.js │ ├── vendor │ │ ├── Export2Excel.js │ │ └── Export2Zip.js │ └── views │ │ ├── dashboard │ │ ├── admin │ │ │ ├── components │ │ │ │ ├── BarChart.vue │ │ │ │ ├── BoxCard.vue │ │ │ │ ├── LineChart.vue │ │ │ │ ├── PanelGroup.vue │ │ │ │ ├── PieChart.vue │ │ │ │ ├── RaddarChart.vue │ │ │ │ ├── TodoList │ │ │ │ │ ├── Todo.vue │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.vue │ │ │ │ ├── TransactionTable.vue │ │ │ │ └── mixins │ │ │ │ │ └── resize.js │ │ │ └── index.vue │ │ ├── editor │ │ │ └── index.vue │ │ └── index.vue │ │ ├── error-page │ │ ├── 401.vue │ │ └── 404.vue │ │ ├── login │ │ ├── auth-redirect.vue │ │ ├── components │ │ │ └── SocialSignin.vue │ │ └── index.vue │ │ ├── monitor │ │ └── server │ │ │ └── index.vue │ │ ├── profile │ │ ├── components │ │ │ ├── Account.vue │ │ │ ├── Activity.vue │ │ │ ├── Timeline.vue │ │ │ └── UserCard.vue │ │ └── index.vue │ │ ├── redirect │ │ └── index.vue │ │ └── system │ │ ├── department │ │ └── index.vue │ │ ├── dict │ │ ├── data.vue │ │ └── index.vue │ │ ├── menu │ │ └── index.vue │ │ ├── permission │ │ └── role.vue │ │ └── user │ │ ├── index.vue │ │ └── profile │ │ ├── index.vue │ │ ├── resetPwd.vue │ │ ├── userAvatar.vue │ │ └── userInfo.vue ├── tests │ └── unit │ │ ├── .eslintrc.js │ │ ├── components │ │ ├── Hamburger.spec.js │ │ └── SvgIcon.spec.js │ │ └── utils │ │ ├── formatTime.spec.js │ │ ├── param2Obj.spec.js │ │ ├── parseTime.spec.js │ │ └── validate.spec.js └── vue.config.js └── logs ├── backend └── .gitkeep └── celery └── .gitkeep /.env: -------------------------------------------------------------------------------- 1 | DOMAIN=localhost 2 | # DOMAIN=local.dockertoolbox.tiangolo.com 3 | # DOMAIN=localhost.tiangolo.com 4 | # DOMAIN=dev.fastapi_src.com 5 | 6 | STACK_NAME=fastapi_src-com 7 | 8 | TRAEFIK_PUBLIC_NETWORK=traefik-public 9 | TRAEFIK_TAG=fastapi_src.com 10 | TRAEFIK_PUBLIC_TAG=traefik-public 11 | 12 | DOCKER_IMAGE_BACKEND=backend 13 | DOCKER_IMAGE_CELERYWORKER=celeryworker 14 | DOCKER_IMAGE_FRONTEND=frontend 15 | 16 | # Backend 17 | BACKEND_CORS_ORIGINS=["http://localhost", "http://localhost:4200", "http://localhost:3000", "http://localhost:8080", "https://localhost", "https://localhost:4200", "https://localhost:3000", "https://localhost:8080", "http://dev.fastapi_src.com", "https://stag.fastapi_src.com", "https://fastapi_src.com", "http://local.dockertoolbox.tiangolo.com", "http://localhost.tiangolo.com"] 18 | PROJECT_NAME=fastapi_src 19 | SECRET_KEY=99d3b1f01aa639e4a76f4fc281fc834747a543720ba4c8a8648ba755aef9be7f 20 | FIRST_SUPERUSER=admin 21 | FIRST_SUPERUSER_PASSWORD=qwe123 22 | SMTP_TLS=True 23 | SMTP_PORT=587 24 | SMTP_HOST= 25 | SMTP_USER= 26 | SMTP_PASSWORD= 27 | EMAILS_FROM_EMAIL=info@fastapi_src.com 28 | 29 | USERS_OPEN_REGISTRATION=False 30 | 31 | SENTRY_DSN= 32 | 33 | # Flower 34 | FLOWER_BASIC_AUTH=admin:qwe123 35 | 36 | # Postgres 37 | POSTGRES_SERVER=49.235.242.224 38 | POSTGRES_USER=root 39 | POSTGRES_PASSWORD=wzx@940516 40 | POSTGRES_DB=DWDB 41 | 42 | # PgAdmin 43 | PGADMIN_LISTEN_PORT=5050 44 | PGADMIN_DEFAULT_EMAIL=admin 45 | PGADMIN_DEFAULT_PASSWORD=qwe123 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /logs/backend/* 2 | /logs/celery/* 3 | *.imi 4 | .idea 5 | .vscode -------------------------------------------------------------------------------- /.idea/vue-element-admin-fastapi.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 wangzhenxiong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/.env: -------------------------------------------------------------------------------- 1 | DOMAIN=localhost 2 | 3 | STACK_NAME=fastapi_src-com 4 | 5 | TRAEFIK_PUBLIC_NETWORK=traefik-public 6 | TRAEFIK_TAG=fastapi_src.com 7 | TRAEFIK_PUBLIC_TAG=traefik-public 8 | 9 | 10 | # Backend 11 | BACKEND_CORS_ORIGINS=["http://localhost", "http://localhost:4200", "http://localhost:3000", "http://localhost:8080", "https://localhost", "https://localhost:4200", "https://localhost:3000", "https://localhost:8080", "http://dev.fastapi_src.com", "https://stag.fastapi_src.com", "https://fastapi_src.com", "http://local.dockertoolbox.tiangolo.com", "http://localhost.tiangolo.com"] 12 | PROJECT_NAME=fastapi_src 13 | SECRET_KEY=99d3b1f01aa639e4a76f4fc281fc834747a543720ba4c8a8648ba755aef9be7f 14 | FIRST_SUPERUSER=admin 15 | FIRST_SUPERUSER_PASSWORD=qwe123 16 | SMTP_TLS=True 17 | SMTP_PORT=587 18 | SMTP_HOST= 19 | SMTP_USER= 20 | SMTP_PASSWORD= 21 | EMAILS_FROM_EMAIL=info@fastapi_src.com 22 | 23 | USERS_OPEN_REGISTRATION=False 24 | 25 | SENTRY_DSN= 26 | 27 | # Flower 28 | FLOWER_BASIC_AUTH=admin:qwe123 29 | 30 | # Postgres 31 | 32 | # PgAdmin 33 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | app.egg-info 3 | .idea 4 | .venv -------------------------------------------------------------------------------- /backend/app/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache 4 | -------------------------------------------------------------------------------- /backend/app/.gitignore: -------------------------------------------------------------------------------- 1 | .mypy_cache 2 | .coverage 3 | htmlcov 4 | /alembic/versions/ 5 | /app/.idea/ 6 | -------------------------------------------------------------------------------- /backend/app/README: -------------------------------------------------------------------------------- 1 | ### python package 2 | ``` 3 | pip install -r pyproject.tml 4 | ``` 5 | #### init db 6 | ``` 7 | sh prestart.sh 8 | ``` 9 | #### app start 10 | ``` 11 | python app/main.py 12 | ``` 13 | 14 | 15 | #### celery start 16 | ``` 17 | sh worker-start.sh 18 | #IF YOU IN WINDOWS YOU SHOULD USE COMMON 19 | # celery worker -A app.celery_app.worker.example -l info -Q main-queue -c 1 -P eventlet 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /backend/app/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /backend/app/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /backend/app/alembic/versions/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/alembic/versions/.keep -------------------------------------------------------------------------------- /backend/app/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/__init__.py -------------------------------------------------------------------------------- /backend/app/app/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/api/__init__.py -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/api/api_v1/__init__.py -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | 3 | from app.api import deps 4 | from app.api.api_v1 import endpoints 5 | from app.api.api_v1 import system 6 | from app.api.api_v1 import report 7 | 8 | api_router = APIRouter() 9 | 10 | api_router.include_router(endpoints.login_router)#登录另起一个路由避免全局dependencies 11 | 12 | # 各自模块的路由由各自模块负责 13 | api_router.include_router(endpoints.router,dependencies=[Depends(deps.get_current_active_user)]) 14 | api_router.include_router(system.router,dependencies=[Depends(deps.get_current_active_user)]) 15 | api_router.include_router(report.router,dependencies=[Depends(deps.get_current_active_user)]) 16 | -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from app.api.api_v1.endpoints import login, utils, role 3 | 4 | login_router = APIRouter() 5 | login_router.include_router(login.router, tags=["login"]) 6 | 7 | router = APIRouter() 8 | # router.include_router(login.router, tags=["login"]) 9 | router.include_router(utils.router, prefix="/utils", tags=["utils"]) 10 | router.include_router(role.router, prefix="/role", tags=["role"]) 11 | -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/endpoints/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from fastapi import APIRouter 4 | from pydantic.networks import EmailStr 5 | 6 | from app import schemas 7 | from app.celery_app.celery_app import celery_app 8 | from app.extensions.utils import send_test_email 9 | 10 | router = APIRouter() 11 | 12 | 13 | @router.post("/test-celery/", response_model=schemas.Msg, status_code=201) 14 | def test_celery(email: schemas.Email,) -> Any: 15 | """ 16 | Test Celery worker. 17 | """ 18 | result = celery_app.send_task("app.celery_app.worker.example.test_celery", args=[email.email]) 19 | # result.get() 20 | return {"msg": "Word received"} 21 | 22 | 23 | @router.post("/test-email/", response_model=schemas.Msg, status_code=201) 24 | def test_email(email_to: EmailStr,) -> Any: 25 | """ 26 | Test emails. 27 | """ 28 | send_test_email(email_to=email_to) 29 | return {"msg": "Test email sent"} 30 | -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/report/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from fastapi import APIRouter, Request, Depends 4 | from sqlalchemy.orm import Session 5 | from fastapi.responses import StreamingResponse 6 | 7 | from app.api import deps 8 | from app.api.api_v1.report.gen_report import Report 9 | 10 | router = APIRouter() 11 | 12 | 13 | @router.get("/report/excel_generate/{excel_name}", tags=["report"]) 14 | def excel_generate(*, excel_name: str = "", request: Request, db: Session = Depends(deps.get_db)) -> Any: 15 | """ 16 | 通过动态import的形式,统一处理excel:模板下载/数据导出 17 | """ 18 | report = Report(code=excel_name, query_params=request.query_params).module 19 | if request.query_params.get("template", "1") == "1": 20 | bio = report.get_template() # 模板 21 | else: 22 | bio = report.get_instance(db) # 实例 23 | file_name = report.file_name.encode('utf-8').decode('latin1') 24 | headers = { 25 | 'Access-Control-Expose-Headers': 'content-disposition', 26 | 'Content-Disposition': f'attachment; filename={file_name}.xlsx' 27 | } 28 | return StreamingResponse(bio, headers=headers) 29 | -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/report/gen_report.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import importlib 3 | from app.api.api_v1.report.gen_excel import gen_template 4 | 5 | 6 | class Report(): 7 | def __init__(self, code, query_params): 8 | self.code = code 9 | self.module_url = "app.api.api_v1.report.report." + code 10 | self.module = self.import_module(self.module_url).Query(query_params=query_params) 11 | 12 | def import_module(self, module_url): 13 | return importlib.import_module(module_url) 14 | 15 | 16 | class BaseQuery(): 17 | def __init__(self, query_params): 18 | self.query_params = query_params 19 | self.header = [] 20 | self.file_name = "" 21 | self.report_config() 22 | 23 | def report_config(self): 24 | pass 25 | 26 | def get_template(self): 27 | return gen_template(self.header, self.file_name) 28 | 29 | def instance_data(self): 30 | return [] 31 | 32 | def get_instance(self, db): 33 | self.db = db 34 | data = self.instance_data() 35 | return gen_template(self.header, self.file_name, data) 36 | -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/report/report/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/api/api_v1/report/report/__init__.py -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/system/__init__.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from app.api.api_v1.system import menu,dict,department,user 3 | 4 | router = APIRouter() 5 | router.include_router(user.router, prefix="/system/user", tags=["system"]) 6 | router.include_router(menu.router, prefix="/system/menu", tags=["system"]) 7 | router.include_router(dict.router, prefix="/system/dict", tags=["system"]) 8 | router.include_router(department.router, prefix="/system/department", tags=["system"]) 9 | 10 | -------------------------------------------------------------------------------- /backend/app/app/api/api_v1/websocket/__init__.py: -------------------------------------------------------------------------------- 1 | import socketio 2 | 3 | from app.api.api_v1.websocket.server import ServerNamespace 4 | 5 | sio = socketio.AsyncServer(async_mode='asgi',cors_allowed_origins='*') 6 | sio.register_namespace(ServerNamespace('/server',)) 7 | 8 | socket_app = socketio.ASGIApp(sio) 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /backend/app/app/celery_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/celery_app/__init__.py -------------------------------------------------------------------------------- /backend/app/app/celery_app/celery_app.py: -------------------------------------------------------------------------------- 1 | from celery import Celery 2 | 3 | # celery_app = Celery("worker", broker="amqp://guest@queue//") 4 | celery_app = Celery( 5 | "worker", 6 | backend="redis://:@49.235.242.224:6379/0", 7 | broker="redis://:@49.235.242.224:6379/1", 8 | ) 9 | # 处理队列 如果不定义会进入默认队列 10 | celery_app.conf.task_routes = {"app.celery_app.worker.example.*": "example-queue"} 11 | -------------------------------------------------------------------------------- /backend/app/app/celery_app/worker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/celery_app/worker/__init__.py -------------------------------------------------------------------------------- /backend/app/app/celery_app/worker/example.py: -------------------------------------------------------------------------------- 1 | # from raven import Client 2 | """ 3 | 启动脚本 - backend/app/celery_worker_start.sh 4 | #linux 5 | # CELERY 4 6 | celery worker -A app.celery_app.worker.example -l info -Q main-queue -c 1 7 | # CELERY 5 8 | celery -A app.celery_app.worker.example worker -l info -Q main-queue -c 1 9 | #win 10 | # CELERY 4 11 | celery worker -A app.celery_app.worker.example -l info -Q main-queue -c 1 -P eventlet 12 | # CELERY 5 13 | celery -A app.celery_app.worker.example worker -l info -Q main-queue -c 1 -P eventlet 14 | 15 | CMD后台启动一个或多个职程 16 | celery multi start w1 -A {worker_name} -l info 17 | """ 18 | from app.celery_app.celery_app import celery_app 19 | from app.extensions.utils import send_test_email 20 | 21 | 22 | # 用于SENTRY异常报告 23 | # from app.core.config import settings 24 | # client_sentry = Client(settings.SENTRY_DSN) 25 | 26 | 27 | @celery_app.task(acks_late=True, ) 28 | def test_celery(email_to: str) -> str: 29 | send_test_email(email_to=email_to) 30 | return f"email sending" 31 | -------------------------------------------------------------------------------- /backend/app/app/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/core/__init__.py -------------------------------------------------------------------------------- /backend/app/app/core/security.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from typing import Any, Union 3 | 4 | from jose import jwt 5 | from passlib.context import CryptContext 6 | 7 | from app.core.config import settings 8 | 9 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 10 | 11 | 12 | ALGORITHM = "HS256" 13 | 14 | 15 | def create_access_token( 16 | subject: Union[str, Any], expires_delta: timedelta = None 17 | ) -> str: 18 | if expires_delta: 19 | expire = datetime.utcnow() + expires_delta 20 | else: 21 | expire = datetime.utcnow() + timedelta( 22 | minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES 23 | ) 24 | to_encode = {"exp": expire, "sub": str(subject)} 25 | encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM) 26 | return encoded_jwt 27 | 28 | 29 | def verify_password(plain_password: str, hashed_password: str) -> bool: 30 | return pwd_context.verify(plain_password, hashed_password) 31 | 32 | 33 | def get_password_hash(password: str) -> str: 34 | return pwd_context.hash(password) 35 | -------------------------------------------------------------------------------- /backend/app/app/crud/__init__.py: -------------------------------------------------------------------------------- 1 | from .crud_user import user 2 | 3 | # For a new basic set of CRUD operations you could just do 4 | 5 | # from .modules import CRUDBase 6 | # from app.models.item import Item 7 | # from app.schemas.item import ItemCreate, ItemUpdate 8 | 9 | # item = CRUDBase[Item, ItemCreate, ItemUpdate](Item) 10 | -------------------------------------------------------------------------------- /backend/app/app/crud/crud_menu.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Optional, Union 2 | 3 | from sqlalchemy.orm import Session 4 | 5 | from app.core.security import get_password_hash, verify_password 6 | from app.crud.base import CRUDBase 7 | from app.models.role import Menu 8 | from app.schemas.system.menu import MenuCreate, MenuUpdate 9 | 10 | 11 | class CRUDMenu(CRUDBase[Menu, MenuCreate, MenuUpdate]): 12 | def get_by_username(self, db: Session, *, username: str) -> Optional[Menu]: 13 | return db.query(Menu).filter(Menu.username == username).first() 14 | 15 | def create(self, db: Session, *, obj_in: MenuCreate) -> Menu: 16 | db_obj = Menu( 17 | username=obj_in.username, 18 | hashed_password=get_password_hash(obj_in.password), 19 | full_name=obj_in.full_name, 20 | is_superuser=obj_in.is_superuser, 21 | ) 22 | db.add(db_obj) 23 | db.commit() 24 | db.refresh(db_obj) 25 | return db_obj 26 | 27 | def update( 28 | self, db: Session, *, db_obj: Menu, obj_in: Union[MenuUpdate, Dict[str, Any]] 29 | ) -> Menu: 30 | if isinstance(obj_in, dict): 31 | update_data = obj_in 32 | else: 33 | update_data = obj_in.dict(exclude_unset=True) 34 | if update_data["password"]: 35 | hashed_password = get_password_hash(update_data["password"]) 36 | del update_data["password"] 37 | update_data["hashed_password"] = hashed_password 38 | return super().update(db, db_obj=db_obj, obj_in=update_data) 39 | 40 | 41 | 42 | 43 | user = CRUDMenu(Menu) 44 | -------------------------------------------------------------------------------- /backend/app/app/crud/crud_user.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Optional, Union 2 | 3 | from sqlalchemy.orm import Session 4 | 5 | from app.core.security import get_password_hash, verify_password 6 | from app.crud.base import CRUDBase 7 | from app.models.user import User 8 | from app.schemas.user import UserCreate, UserUpdate 9 | 10 | 11 | class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): 12 | def get_by_username(self, db: Session, *, username: str) -> Optional[User]: 13 | return db.query(User).filter(User.username == username).first() 14 | 15 | def authenticate(self, db: Session, *, username: str, password: str) -> Optional[User]: 16 | user = self.get_by_username(db, username=username) 17 | if not user: 18 | return None 19 | if not verify_password(password, user.hashed_password): 20 | return None 21 | return user 22 | 23 | def is_active(self, user: User) -> bool: 24 | return user.is_active 25 | 26 | def is_superuser(self, user: User) -> bool: 27 | return user.is_superuser 28 | 29 | 30 | user = CRUDUser(User) 31 | -------------------------------------------------------------------------------- /backend/app/app/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/db/__init__.py -------------------------------------------------------------------------------- /backend/app/app/db/base.py: -------------------------------------------------------------------------------- 1 | # Import all the models, so that Base has them before being 2 | # imported by Alembic 3 | from app.db.base_class import Base # noqa 4 | from app.models.user import User # noqa 5 | -------------------------------------------------------------------------------- /backend/app/app/db/base_class.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from sqlalchemy.ext.declarative import as_declarative, declared_attr 4 | 5 | 6 | @as_declarative() 7 | class Base: 8 | id: Any 9 | __name__: str 10 | 11 | # Generate __tablename__ automatically 12 | @declared_attr 13 | def __tablename__(cls) -> str: 14 | return cls.__name__.lower() 15 | 16 | def dict(self): 17 | return {c.name: getattr(self, c.name, None) for c in self.__table__.columns} 18 | 19 | def list(self): 20 | return [getattr(self, c.name, None) for c in self.__table__.columns] 21 | -------------------------------------------------------------------------------- /backend/app/app/db/export_data.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import os 3 | from app.db.session import SessionLocal,engine 4 | db = SessionLocal() 5 | sql = """ 6 | SELECT TABLE_NAME FROM information_schema.`TABLES` WHERE TABLE_SCHEMA = 'DWDB' AND TABLE_NAME != 'alembic_version' 7 | """ 8 | rows = db.execute(sql).fetchall() 9 | for table in rows: 10 | table_name = table[0] 11 | sql = f""" 12 | SELECT * FROM DWDB.{table_name } 13 | """ 14 | 15 | df = pd.read_sql(sql,con=engine) 16 | file_path = os.path.join(os.path.dirname(__file__) ,"init_data",f"{table_name}.csv") 17 | print(file_path) 18 | df.to_csv(file_path,index=0) 19 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/department.csv: -------------------------------------------------------------------------------- 1 | id,code,name,order,parent_id,status,start_date,end_date 2 | 1,000,总公司,1,,1,, 3 | 2,0000,总部,4,1.0,1,, 4 | 3,0001,子公司0001,3,1.0,1,, 5 | 4,0002,子公司0002,3,1.0,1,, 6 | 5,000T,科技子公司,4,1.0,1,, 7 | 6,0000A,董事会0000A,1,2.0,1,, 8 | 7,0000B,业务部0000B,2,2.0,1,, 9 | 8,0000C,财务部0000C,3,2.0,1,, 10 | 9,0000D,人事部0000D,4,2.0,1,, 11 | 10,0000T,科技部门,1,5.0,1,, 12 | 11,0000T_Y,运维部门,2,5.0,1,2021-01-28,3000-12-31 13 | 12,0000T_S,安全部门,3,5.0,1,2021-01-28,3000-12-31 14 | 13,000T_T,测试部门,4,5.0,1,2021-01-28,3000-12-31 15 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/dict_data.csv: -------------------------------------------------------------------------------- 1 | id,label,order,remark,type_id,css_class,list_class,is_default 2 | 1,董事长,1,,1,,, 3 | 2,总经理,2,,1,,, 4 | 3,副总经理,3,,1,,, 5 | 4,助理,4,,1,,, 6 | 5,普通员工,5,,1,,, 7 | 6,男,1,,2,,,0.0 8 | 7,女,2,,2,,,0.0 9 | 8,实习,1,,3,,,0.0 10 | 9,在职,2,,3,,,0.0 11 | 10,离职,3,,3,,,0.0 12 | 11,退休,4,,3,,,0.0 13 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/dict_type.csv: -------------------------------------------------------------------------------- 1 | id,code,name,description 2 | 1,post,岗位,员工岗位列表 3 | 2,sex,性别, 4 | 3,user_status,用户状态, 5 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/menu.csv: -------------------------------------------------------------------------------- 1 | id,path,component,external_link,name,title,icon,no_cache,affix,order,parent_id 2 | 519,/system,,0,,系统管理,system,0,0,1, 3 | 516,role,/system/permission/role,0,RolePermission,角色权限,peoples,1,0,5,519.0 4 | 520,menu,/system/menu/index,0,Menu,菜单管理,tree-table,0,0,2,519.0 5 | 522,dict,/system/dict/index,0,DictType,字典管理,dict,0,0,4,519.0 6 | 524,department,/system/department/index,0,Department,部门管理,tree,0,0,3,519.0 7 | 525,user,/system/user/index,0,User,用户管理,user,0,0,1,519.0 8 | 526,/monitor,,0,,系统监控,monitor,0,0,2, 9 | 527,server,/monitor/server/index,0,Server,服务监控,dashboard,0,0,1,526.0 10 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/role.csv: -------------------------------------------------------------------------------- 1 | id,name,description,order 2 | 1,admin,管理员,1 3 | 2,editor,编辑人员,2 4 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/role_menu.csv: -------------------------------------------------------------------------------- 1 | id,role_id,menu_id 2 | 1302,1,519 3 | 1303,1,516 4 | 1304,1,520 5 | 1305,1,522 6 | 1306,1,524 7 | 1307,1,525 8 | 1308,1,526 9 | 1309,1,527 10 | 1331,2,519 11 | 1332,2,516 12 | 1333,2,520 13 | 1334,2,522 14 | 1335,2,524 15 | 1336,2,525 16 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/user.csv: -------------------------------------------------------------------------------- 1 | id,username,nickname,sex,identity_card,phone,address,work_start,hashed_password,avatar,introduction,status,is_active,is_superuser 2 | 1,admin,系统管理员,男,999999999999999999,19999999999,,,$2b$12$87IEjnhNdBetJ0LFe7jd5ubpM9FybAu9MraIi8ObUzfRPfKB5B0uS,https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif,管理员账户,在职,1,1 3 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/user_department.csv: -------------------------------------------------------------------------------- 1 | id,user_id,department_id 2 | 57,1,11 3 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/user_dict.csv: -------------------------------------------------------------------------------- 1 | id,user_id,dict_id 2 | 76,1,1 3 | -------------------------------------------------------------------------------- /backend/app/app/db/init_data/user_role.csv: -------------------------------------------------------------------------------- 1 | id,user_id,role_id 2 | 85,1,1 3 | -------------------------------------------------------------------------------- /backend/app/app/db/init_db.py: -------------------------------------------------------------------------------- 1 | import os, logging 2 | import pandas as pd 3 | import numpy as np 4 | from app.db.session import engine,SessionLocal 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | def init_db() -> None: 9 | session = SessionLocal() 10 | # Tables should be created with Alembic migrations 11 | init_data_path = os.path.join(os.path.dirname(__file__), "init_data") 12 | files = ['department.csv', 'menu.csv', 'role.csv', 'user.csv', 'dict_type.csv', 'dict_data.csv', 13 | 'role_menu.csv', 'user_department.csv', 'user_dict.csv', 'user_role.csv', ] 14 | for file in files: 15 | file_path = os.path.join(init_data_path, file) 16 | df = pd.read_csv(file_path, sep=",") 17 | if file == "menu.csv": 18 | df['component'] = df['component'].apply(lambda x: '' if pd.isnull(x) else x) 19 | df['name'] = df['name'].apply(lambda x: '' if pd.isnull(x) else x) 20 | logger.info(f"{file} load successed") 21 | table_name = file.replace(".csv", "") 22 | df.to_sql(table_name, engine, if_exists="append", index=False) 23 | sql = f"ALTER TABLE {table_name} AUTO_INCREMENT = {max(df['id']) + 1}" 24 | session.execute(sql) 25 | session.commit() 26 | session.close() 27 | -------------------------------------------------------------------------------- /backend/app/app/db/session.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.orm import sessionmaker 3 | 4 | from app.core.config import settings 5 | 6 | engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True) 7 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 8 | -------------------------------------------------------------------------------- /backend/app/app/db_pre_start/backend_pre_start.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.sys.path.append(os.path.join(os.path.dirname(__file__), "..","..")) 4 | 5 | import logging 6 | 7 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed 8 | 9 | from app.db.session import SessionLocal 10 | 11 | logging.basicConfig(level=logging.INFO) 12 | logger = logging.getLogger(__name__) 13 | 14 | max_tries = 60 * 5 # 5 minutes 15 | wait_seconds = 1 16 | 17 | 18 | @retry( 19 | stop=stop_after_attempt(max_tries), 20 | wait=wait_fixed(wait_seconds), 21 | before=before_log(logger, logging.INFO), 22 | after=after_log(logger, logging.WARN), 23 | ) 24 | def init() -> None: 25 | try: 26 | db = SessionLocal() 27 | # Try to create session to check if DB is awake 28 | db.execute("SELECT 1") 29 | except Exception as e: 30 | logger.error(e) 31 | raise e 32 | 33 | 34 | def main() -> None: 35 | logger.info("Initializing service") 36 | init() 37 | logger.info("Service finished initializing") 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /backend/app/app/db_pre_start/tests_pre_start.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed 4 | 5 | from app.db.session import SessionLocal 6 | 7 | logging.basicConfig(level=logging.INFO) 8 | logger = logging.getLogger(__name__) 9 | 10 | max_tries = 60 * 5 # 5 minutes 11 | wait_seconds = 1 12 | 13 | 14 | @retry( 15 | stop=stop_after_attempt(max_tries), 16 | wait=wait_fixed(wait_seconds), 17 | before=before_log(logger, logging.INFO), 18 | after=after_log(logger, logging.WARN), 19 | ) 20 | def init() -> None: 21 | try: 22 | # Try to create session to check if DB is awake 23 | db = SessionLocal() 24 | db.execute("SELECT 1") 25 | except Exception as e: 26 | logger.error(e) 27 | raise e 28 | 29 | 30 | def main() -> None: 31 | logger.info("Initializing service") 32 | init() 33 | logger.info("Service finished initializing") 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /backend/app/app/email-templates/src/new_account.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ project_name }} - New Account 7 | You have a new account: 8 | Username: {{ username }} 9 | Password: {{ password }} 10 | Go to Dashboard 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /backend/app/app/email-templates/src/reset_password.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ project_name }} - Password Recovery 7 | We received a request to recover the password for user {{ username }} 8 | with email {{ email }} 9 | Reset your password by clicking the button below: 10 | Reset Password 11 | Or open the following link: 12 | {{ link }} 13 | 14 | The reset password link / button will expire in {{ valid_hours }} hours. 15 | If you didn't request a password recovery you can disregard this email. 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /backend/app/app/email-templates/src/test_email.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ project_name }} 7 | Test email for: {{ email }} 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/app/app/extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/extensions/__init__.py -------------------------------------------------------------------------------- /backend/app/app/extensions/exception.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request 2 | from starlette.responses import JSONResponse 3 | from fastapi.responses import PlainTextResponse 4 | from starlette.exceptions import HTTPException as StarletteHTTPException 5 | 6 | 7 | #重写异常 8 | def register_exception(app: FastAPI): 9 | @app.exception_handler(Exception) 10 | async def unicorn_exception_handler(request: Request, exc: Exception): 11 | return JSONResponse( 12 | status_code=418, 13 | content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}, 14 | ) 15 | 16 | @app.exception_handler(StarletteHTTPException) 17 | async def http_exception_handler(request, exc): 18 | return PlainTextResponse(str(exc.detail), status_code=exc.status_code) 19 | -------------------------------------------------------------------------------- /backend/app/app/extensions/request_id.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from starlette_context import context 4 | 5 | REQUEST_ID_CTX_KEY = 'X-Request-ID' 6 | 7 | def current_request_id(): 8 | """get current request_id inside context var""" 9 | return context.data[REQUEST_ID_CTX_KEY] 10 | 11 | 12 | class RequestIDLogFilter(logging.Filter): 13 | 14 | def filter(self, log): 15 | log.request_id = current_request_id() 16 | return log 17 | -------------------------------------------------------------------------------- /backend/app/app/initial_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.sys.path.append(os.path.join(os.path.dirname(__file__), "..")) 4 | 5 | import logging 6 | 7 | from app.db.init_db import init_db 8 | 9 | logging.basicConfig(level=logging.INFO) 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def init() -> None: 14 | init_db() 15 | 16 | def main() -> None: 17 | logger.info("Creating initial data") 18 | init() 19 | logger.info("Initial data created") 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /backend/app/app/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | # TO SUPPORT RUN python main.py in windows,but I use python "app/main.py" to start in liunx 3 | os.sys.path.append(os.path.join(os.path.dirname(__file__), "..")) 4 | 5 | from fastapi import FastAPI 6 | from app.core.config import settings 7 | from app.api.api_v1.api import api_router 8 | from app.api.api_v1.websocket import socket_app 9 | from app.middleware import register_middleware 10 | from app.extensions.logger import LOGGING_CONFIG 11 | 12 | # app 13 | app = FastAPI( 14 | title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json" 15 | ) 16 | # set middleware 17 | register_middleware(app) 18 | 19 | # set router 20 | app.include_router(api_router, prefix=settings.API_V1_STR) 21 | # set socketio 22 | app.mount('/', socket_app) 23 | 24 | if __name__ == '__main__': 25 | import uvicorn 26 | 27 | # Don't set debug/reload equals True,becauese TimedRotatingFileHandler can't support multi-prcoess 28 | # or dont't use my LOGGING_CONFIG in debug/reload 29 | uvicorn.run(app='main:app', host="0.0.0.0", port=8080, log_config=LOGGING_CONFIG) 30 | -------------------------------------------------------------------------------- /backend/app/app/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | from starlette.middleware.cors import CORSMiddleware 2 | 3 | from app.core.config import settings 4 | from app.middleware.access_middle import AccessMiddleware 5 | from starlette_context.middleware import RawContextMiddleware 6 | from starlette_context import plugins 7 | 8 | 9 | def register_middleware(app): 10 | # midddleware fastapi是逆序注册的 所以最后注册RequestIdPlugin log reqeust_id 好让其他middleware使用 11 | app.add_middleware(AccessMiddleware) 12 | 13 | app.add_middleware(RawContextMiddleware, plugins=(plugins.RequestIdPlugin(),)) 14 | 15 | if settings.BACKEND_CORS_ORIGINS: 16 | app.add_middleware( 17 | CORSMiddleware, 18 | # allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS], 19 | allow_origins=["*"], 20 | allow_credentials=True, 21 | allow_methods=["*"], 22 | allow_headers=["*"], 23 | ) 24 | -------------------------------------------------------------------------------- /backend/app/app/middleware/access_middle.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from fastapi import Request 3 | from starlette.middleware.base import BaseHTTPMiddleware 4 | from app.extensions.logger import backend_logger 5 | 6 | 7 | class AccessMiddleware(BaseHTTPMiddleware): 8 | async def dispatch(self, request: Request, call_next): 9 | backend_logger.info(f"{request.client.host} {request.method} {request.url} [I]") 10 | try: 11 | response = await call_next(request) 12 | except Exception as exc: 13 | backend_logger.exception(exc) 14 | raise exc 15 | backend_logger.info(f"{request.client.host} {request.method} {request.url} [0]") 16 | return response 17 | -------------------------------------------------------------------------------- /backend/app/app/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .user import * 2 | from .role import * 3 | from .system import * 4 | -------------------------------------------------------------------------------- /backend/app/app/models/role.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String, ForeignKey 2 | from sqlalchemy.orm import relationship 3 | from app.db.base_class import Base 4 | 5 | 6 | class Role(Base): 7 | """权限组""" 8 | id = Column(Integer, primary_key=True, index=True, autoincrement=True) 9 | name = Column(String(32), doc="权限组名称") 10 | description = Column(String(128), doc="备注") 11 | order = Column(Integer, doc="顺序") 12 | 13 | role_menu = relationship("Role_Menu", backref="role") 14 | 15 | 16 | class Role_Menu(Base): 17 | """权限组-菜单-中间表""" 18 | id = Column(Integer, primary_key=True, index=True, autoincrement=True) 19 | role_id = Column(Integer, ForeignKey("role.id", ondelete='CASCADE')) 20 | menu_id = Column(Integer, ForeignKey("menu.id", ondelete='CASCADE')) 21 | -------------------------------------------------------------------------------- /backend/app/app/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | from .msg import Msg,Email 2 | from .token import Token, TokenPayload 3 | from .user import * 4 | from .reponse import Response 5 | from .role import * 6 | from .system.menu import * 7 | from .system.dict import * 8 | from .system.department import * 9 | -------------------------------------------------------------------------------- /backend/app/app/schemas/msg.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from pydantic.networks import EmailStr 3 | 4 | class Msg(BaseModel): 5 | msg: str 6 | 7 | class Email(BaseModel): 8 | email: EmailStr 9 | -------------------------------------------------------------------------------- /backend/app/app/schemas/reponse.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | # Shared properties 7 | class Response(BaseModel): 8 | code: Optional[int] = None 9 | data : Optional[Any] = None 10 | message: Optional[str] = None 11 | -------------------------------------------------------------------------------- /backend/app/app/schemas/role.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | # Shared properties 7 | class RoleBase(BaseModel): 8 | id: Optional[str] = None 9 | name: Optional[str] = None 10 | description: str 11 | order: Optional[str] = None 12 | routes: Any = None 13 | 14 | 15 | # Properties to receive on item creation 16 | class RoleCreate(RoleBase): 17 | pass 18 | 19 | 20 | # Properties to receive on item update 21 | class RoleUpdate(RoleBase): 22 | pass 23 | -------------------------------------------------------------------------------- /backend/app/app/schemas/system/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/schemas/system/__init__.py -------------------------------------------------------------------------------- /backend/app/app/schemas/system/department.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | # Shared properties 7 | class DepartmentBase(BaseModel): 8 | code: str = None 9 | name: str = None 10 | order: int = None 11 | parent_id: Optional[int] = None 12 | status: bool = True 13 | start_date: Optional[str] = None 14 | end_date: Optional[str] = None 15 | 16 | 17 | # Properties to receive on item creation 18 | class DepartmentCreate(DepartmentBase): 19 | pass 20 | 21 | 22 | # Properties to receive on item update 23 | class DepartmentUpdate(DepartmentBase): 24 | id: int 25 | -------------------------------------------------------------------------------- /backend/app/app/schemas/system/dict.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | # Shared properties 7 | class DictTypeBase(BaseModel): 8 | name: str 9 | code: str 10 | description: Optional[str] = None 11 | 12 | 13 | # Properties to receive on item creation 14 | class DictTypeCreate(DictTypeBase): 15 | pass 16 | 17 | 18 | # Properties to receive on item update 19 | class DictTypeUpdate(DictTypeBase): 20 | id: int 21 | 22 | 23 | class DictDataBase(BaseModel): 24 | """字典明细""" 25 | label: str 26 | order: int 27 | remark: Optional[str] = None 28 | type_id: str 29 | css_class: Optional[str] = None 30 | list_class: Optional[str] = None 31 | is_default: Optional[bool] = None 32 | 33 | 34 | class DictDataCreate(DictDataBase): 35 | pass 36 | 37 | 38 | class DictDataUpdate(DictDataBase): 39 | id: int 40 | -------------------------------------------------------------------------------- /backend/app/app/schemas/system/menu.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from pydantic import BaseModel 3 | 4 | 5 | # Shared properties 6 | class MenuBase(BaseModel): 7 | path: str = "" 8 | component: Optional[str] = None 9 | affix: Optional[bool] = False 10 | external_link: Optional[bool] = False 11 | parent_id: Optional[int] = None 12 | 13 | name: Optional[str] = None 14 | title: str = "" 15 | icon: str = "" 16 | no_cache: bool = False 17 | order: int = 0 18 | 19 | 20 | # Properties to receive on item creation 21 | class MenuCreate(MenuBase): 22 | pass 23 | 24 | 25 | # Properties to receive on item update 26 | class MenuUpdate(MenuBase): 27 | id: int 28 | -------------------------------------------------------------------------------- /backend/app/app/schemas/token.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Token(BaseModel): 7 | access_token: str 8 | token_type: str 9 | 10 | class TokenPayload(BaseModel): 11 | sub: Optional[int] = None 12 | -------------------------------------------------------------------------------- /backend/app/app/schemas/user.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from typing import Optional, List 4 | from pydantic import BaseModel 5 | 6 | from app.core.security import get_password_hash 7 | from app.core.config import settings 8 | 9 | 10 | # Shared properties 11 | class UserBase(BaseModel): 12 | username: str 13 | nickname: str 14 | sex: str 15 | identity_card: str 16 | phone: Optional[str] = None 17 | # address: Optional[str] = None 18 | work_start: Optional[str] = datetime.datetime.today() 19 | status: Optional[str] = None 20 | hashed_password: str = get_password_hash(settings.INIT_PASSWORD) 21 | # avatar: Optional[str] = None 22 | introduction: Optional[str] = None 23 | is_active: Optional[bool] = True 24 | 25 | 26 | # Properties to receive via API on creation 27 | class UserCreate(UserBase): 28 | deptId: Optional[int] = None 29 | postIds: List[int] = [] 30 | roleIds: List[int] = [] 31 | 32 | 33 | # Properties to receive via API on update 34 | class UserUpdate(UserBase): 35 | id: Optional[int] = None 36 | deptId: Optional[int] = None 37 | postIds: List[int] = [] 38 | roleIds: List[int] = [] 39 | 40 | 41 | # reset password 42 | class UserPWReset(BaseModel): 43 | user_id: int 44 | password: str 45 | -------------------------------------------------------------------------------- /backend/app/app/tests/.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | -------------------------------------------------------------------------------- /backend/app/app/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/tests/__init__.py -------------------------------------------------------------------------------- /backend/app/app/tests/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/tests/api/__init__.py -------------------------------------------------------------------------------- /backend/app/app/tests/api/api_v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/tests/api/api_v1/__init__.py -------------------------------------------------------------------------------- /backend/app/app/tests/api/api_v1/test_celery.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from fastapi.testclient import TestClient 4 | 5 | from app.core.config import settings 6 | 7 | 8 | def test_celery_worker_test( 9 | client: TestClient, superuser_token_headers: Dict[str, str] 10 | ) -> None: 11 | data = {"msg": "test"} 12 | r = client.post( 13 | f"{settings.API_V1_STR}/utils/test-celery/", 14 | json=data, 15 | headers=superuser_token_headers, 16 | ) 17 | response = r.json() 18 | assert response["msg"] == "Word received" 19 | -------------------------------------------------------------------------------- /backend/app/app/tests/api/api_v1/test_login.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | import os 3 | os.sys.path.append("C:\\Users\\Mr.Wang\\Desktop\\myfile\\fastapi_src\\backend\\app") 4 | 5 | from fastapi.testclient import TestClient 6 | 7 | from app.core.config import settings 8 | 9 | 10 | def test_get_access_token(client: TestClient) -> None: 11 | login_data = { 12 | "username": settings.FIRST_SUPERUSER, 13 | "password": settings.FIRST_SUPERUSER_PASSWORD, 14 | } 15 | r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data) 16 | tokens = r.json() 17 | assert r.status_code == 200 18 | assert "access_token" in tokens 19 | assert tokens["access_token"] 20 | 21 | 22 | def test_use_access_token( 23 | client: TestClient, superuser_token_headers: Dict[str, str] 24 | ) -> None: 25 | r = client.post( 26 | f"{settings.API_V1_STR}/login/test-token", headers=superuser_token_headers, 27 | ) 28 | result = r.json() 29 | assert r.status_code == 200 30 | assert "email" in result 31 | 32 | -------------------------------------------------------------------------------- /backend/app/app/tests/conftest.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Generator 2 | 3 | import pytest 4 | from fastapi.testclient import TestClient 5 | from sqlalchemy.orm import Session 6 | 7 | from app.core.config import settings 8 | from app.db.session import SessionLocal 9 | from app.main import app 10 | from app.tests.utils.user import authentication_token_from_email 11 | from app.tests.utils.utils import get_superuser_token_headers 12 | 13 | 14 | @pytest.fixture(scope="session") 15 | def db() -> Generator: 16 | yield SessionLocal() 17 | 18 | 19 | @pytest.fixture(scope="module") 20 | def client() -> Generator: 21 | with TestClient(app) as c: 22 | yield c 23 | 24 | 25 | @pytest.fixture(scope="module") 26 | def superuser_token_headers(client: TestClient) -> Dict[str, str]: 27 | return get_superuser_token_headers(client) 28 | 29 | 30 | @pytest.fixture(scope="module") 31 | def normal_user_token_headers(client: TestClient, db: Session) -> Dict[str, str]: 32 | return authentication_token_from_email( 33 | client=client, email=settings.EMAIL_TEST_USER, db=db 34 | ) 35 | -------------------------------------------------------------------------------- /backend/app/app/tests/crud/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/tests/crud/__init__.py -------------------------------------------------------------------------------- /backend/app/app/tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyfavour/vue-element-admin-fastapi/1f4ca4aaf2bc7edfd3f3d71e7c42c0236748e457/backend/app/app/tests/utils/__init__.py -------------------------------------------------------------------------------- /backend/app/app/tests/utils/utils.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | from typing import Dict 4 | 5 | from fastapi.testclient import TestClient 6 | 7 | from app.core.config import settings 8 | 9 | 10 | def random_lower_string() -> str: 11 | return "".join(random.choices(string.ascii_lowercase, k=32)) 12 | 13 | 14 | def random_email() -> str: 15 | return f"{random_lower_string()}@{random_lower_string()}.com" 16 | 17 | 18 | def get_superuser_token_headers(client: TestClient) -> Dict[str, str]: 19 | login_data = { 20 | "username": settings.FIRST_SUPERUSER, 21 | "password": settings.FIRST_SUPERUSER_PASSWORD, 22 | } 23 | r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data) 24 | tokens = r.json() 25 | a_token = tokens["access_token"] 26 | headers = {"Authorization": f"Bearer {a_token}"} 27 | return headers 28 | -------------------------------------------------------------------------------- /backend/app/main-start.sh: -------------------------------------------------------------------------------- 1 | ps -ux|grep python|grep main.py|awk '{print $2}'|awk -F '/' '{print $1}'|xargs kill -9 2 | nohup python app/main.py >> server.log & 3 | -------------------------------------------------------------------------------- /backend/app/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | plugins = pydantic.mypy, sqlmypy 3 | ignore_missing_imports = True 4 | disallow_untyped_defs = True 5 | -------------------------------------------------------------------------------- /backend/app/prestart.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # Let the DB start 4 | python ./app/db_pre_start/backend_pre_start.py 5 | 6 | # Run migrations 7 | alembic revision --autogenerate -m "first commit" 8 | 9 | alembic upgrade head 10 | 11 | # Create initial data in DB 12 | python ./app/initial_data.py 13 | -------------------------------------------------------------------------------- /backend/app/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "app" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | 7 | [tool.poetry.dependencies] 8 | python = ">=3.7.1,<4.0" 9 | fastapi = "^0.65.2" 10 | SQLAlchemy = "^1.3.23" 11 | python-jose = "^3.2.0" 12 | passlib = "^1.7.4" 13 | celery = "^5.0.5" 14 | pandas = "^1.2.1" 15 | numpy = "^1.20.0" 16 | uvicorn = "^0.13.3" 17 | tenacity = "^6.3.1" 18 | emails = "^0.6" 19 | openpyxl = "^3.0.6" 20 | psutil = "^5.8.0" 21 | alembic = "^1.5.4" 22 | python-socketio = "^5.0.4" 23 | mysqlclient = "^2.0.3" 24 | redis = "^3.5.3" 25 | bcrypt = "^3.2.0" 26 | 27 | [tool.poetry.dev-dependencies] 28 | pytest = "^6.2.2" 29 | black = "^20.8b1" 30 | 31 | [build-system] 32 | requires = ["poetry-core>=1.0.0"] 33 | build-backend = "poetry.core.masonry.api" 34 | -------------------------------------------------------------------------------- /backend/app/requirements.txt: -------------------------------------------------------------------------------- 1 | uvicorn[standard] 2 | fastapi 3 | python-multipart 4 | email-validator 5 | requests 6 | celery 7 | passlib 8 | tenacity 9 | pydantic 10 | emails 11 | raven 12 | gunicorn 13 | jinja2 14 | psycopg2-binary 15 | alembic 16 | sqlalchemy==1.3.22 17 | pytest 18 | python-jose 19 | aiofiles 20 | openpyxl 21 | python-socketio 22 | psutil 23 | bcrypt 24 | mysqlclient 25 | redis 26 | numpy 27 | starlette-context 28 | -------------------------------------------------------------------------------- /backend/app/scripts/format-imports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | set -x 3 | 4 | # Sort imports one per line, so autoflake can remove unused imports 5 | isort --recursive --force-single-line-imports --apply app 6 | sh ./scripts/format.sh 7 | -------------------------------------------------------------------------------- /backend/app/scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | set -x 3 | 4 | autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place app --exclude=__init__.py 5 | black app 6 | isort --recursive --apply app 7 | -------------------------------------------------------------------------------- /backend/app/scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | mypy app 6 | black app --check 7 | isort --recursive --check-only app 8 | flake8 9 | -------------------------------------------------------------------------------- /backend/app/scripts/test-cov-html.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -x 5 | 6 | bash scripts/test.sh --cov-report=html "${@}" 7 | -------------------------------------------------------------------------------- /backend/app/scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -x 5 | 6 | pytest --cov=app --cov-report=term-missing app/tests "${@}" 7 | -------------------------------------------------------------------------------- /backend/app/tests-start.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -e 3 | 4 | python /app/app/tests_pre_start.py 5 | 6 | bash ./scripts/test.sh "$@" 7 | -------------------------------------------------------------------------------- /backend/app/worker-start.sh: -------------------------------------------------------------------------------- 1 | #指明队列名称 2 | ps -ux|grep 'celery'|grep -v grep|awk '{print $2}'|xargs kill -9 3 | nohup celery -A app.celery_app.worker.example worker -l info -Q example-queue -c 1 > celery.log & 4 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /frontend/.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'http://127.0.0.1:8080' 6 | # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, 7 | # to control whether the babel-plugin-dynamic-import-node plugin is enabled. 8 | # It only does one thing by converting all import() to require(). 9 | # This configuration can significantly increase the speed of hot updates, 10 | # when you have a large number of pages. 11 | # Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js 12 | 13 | # VUE_CLI_BABEL_TRANSPILE_MODULES = true 14 | -------------------------------------------------------------------------------- /frontend/.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'http://127.0.0.1:8080' 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/.env.staging: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | 3 | # just a flag 4 | ENV = 'staging' 5 | 6 | # base api 7 | VUE_APP_BASE_API = '/stage-api' 8 | 9 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | src/assets 3 | public 4 | dist 5 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | **/*.log 8 | 9 | tests/**/coverage/ 10 | tests/e2e/reports 11 | selenium-debug.log 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.local 21 | 22 | package-lock.json 23 | yarn.lock 24 | -------------------------------------------------------------------------------- /frontend/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 10 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /frontend/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present PanJiaChen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ], 5 | env: { 6 | development: { 7 | plugins: ['dynamic-import-node'] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/build/index.js: -------------------------------------------------------------------------------- 1 | const { run } = require('runjs') 2 | const chalk = require('chalk') 3 | const config = require('../vue.config.js') 4 | const rawArgv = process.argv.slice(2) 5 | const args = rawArgv.join(' ') 6 | 7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) { 8 | const report = rawArgv.includes('--report') 9 | 10 | run(`vue-cli-service build ${args}`) 11 | 12 | const port = 9526 13 | const publicPath = config.publicPath 14 | 15 | var connect = require('connect') 16 | var serveStatic = require('serve-static') 17 | const app = connect() 18 | 19 | app.use( 20 | publicPath, 21 | serveStatic('./dist', { 22 | index: ['index.html', '/'] 23 | }) 24 | ) 25 | 26 | app.listen(port, function () { 27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) 28 | if (report) { 29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) 30 | } 31 | 32 | }) 33 | } else { 34 | run(`vue-cli-service build ${args}`) 35 | } 36 | -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | testMatch: [ 14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 15 | ], 16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], 17 | coverageDirectory: '/tests/unit/coverage', 18 | // 'collectCoverage': true, 19 | 'coverageReporters': [ 20 | 'lcov', 21 | 'text-summary' 22 | ], 23 | testURL: 'http://localhost/' 24 | } 25 | -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } -------------------------------------------------------------------------------- /frontend/mock/index.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs') 2 | const { param2Obj } = require('./utils') 3 | 4 | const user = require('./user') 5 | const role = require('./role') 6 | const article = require('./article') 7 | const search = require('./remote-search') 8 | 9 | const mocks = [ 10 | ...user, 11 | ...role, 12 | ...article, 13 | ...search 14 | ] 15 | 16 | // for front mock 17 | // please use it cautiously, it will redefine XMLHttpRequest, 18 | // which will cause many of your third-party libraries to be invalidated(like progress event). 19 | function mockXHR() { 20 | // mock patch 21 | // https://github.com/nuysoft/Mock/issues/300 22 | Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send 23 | Mock.XHR.prototype.send = function() { 24 | if (this.custom.xhr) { 25 | this.custom.xhr.withCredentials = this.withCredentials || false 26 | 27 | if (this.responseType) { 28 | this.custom.xhr.responseType = this.responseType 29 | } 30 | } 31 | this.proxy_send(...arguments) 32 | } 33 | 34 | function XHR2ExpressReqWrap(respond) { 35 | return function(options) { 36 | let result = null 37 | if (respond instanceof Function) { 38 | const { body, type, url } = options 39 | // https://expressjs.com/en/4x/api.html#req 40 | result = respond({ 41 | method: type, 42 | body: JSON.parse(body), 43 | query: param2Obj(url) 44 | }) 45 | } else { 46 | result = respond 47 | } 48 | return Mock.mock(result) 49 | } 50 | } 51 | 52 | for (const i of mocks) { 53 | Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response)) 54 | } 55 | } 56 | 57 | module.exports = { 58 | mocks, 59 | mockXHR 60 | } 61 | -------------------------------------------------------------------------------- /frontend/mock/remote-search.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs') 2 | 3 | const NameList = [] 4 | const count = 100 5 | 6 | for (let i = 0; i < count; i++) { 7 | NameList.push(Mock.mock({ 8 | name: '@first' 9 | })) 10 | } 11 | NameList.push({ name: 'mock-Pan' }) 12 | 13 | module.exports = [ 14 | // username search 15 | { 16 | url: '/vue-element-admin/search/user', 17 | type: 'get', 18 | response: config => { 19 | const { name } = config.query 20 | const mockNameList = NameList.filter(item => { 21 | const lowerCaseName = item.name.toLowerCase() 22 | return !(name && lowerCaseName.indexOf(name.toLowerCase()) < 0) 23 | }) 24 | return { 25 | code: 20000, 26 | data: { items: mockNameList } 27 | } 28 | } 29 | }, 30 | 31 | // transaction list 32 | { 33 | url: '/vue-element-admin/transaction/list', 34 | type: 'get', 35 | response: _ => { 36 | return { 37 | code: 20000, 38 | data: { 39 | total: 20, 40 | 'items|20': [{ 41 | order_no: '@guid()', 42 | timestamp: +Mock.Random.date('T'), 43 | username: '@name()', 44 | price: '@float(1000, 15000, 0, 2)', 45 | 'status|1': ['success', 'pending'] 46 | }] 47 | } 48 | } 49 | } 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /frontend/mock/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} url 3 | * @returns {Object} 4 | */ 5 | function param2Obj(url) { 6 | const search = url.split('?')[1] 7 | if (!search) { 8 | return {} 9 | } 10 | return JSON.parse( 11 | '{"' + 12 | decodeURIComponent(search) 13 | .replace(/"/g, '\\"') 14 | .replace(/&/g, '","') 15 | .replace(/=/g, '":"') 16 | .replace(/\+/g, ' ') + 17 | '"}' 18 | ) 19 | } 20 | 21 | /** 22 | * This is just a simple version of deep copy 23 | * Has a lot of edge cases bug 24 | * If you want to use a perfect deep copy, use lodash's _.cloneDeep 25 | * @param {Object} source 26 | * @returns {Object} 27 | */ 28 | function deepClone(source) { 29 | if (!source && typeof source !== 'object') { 30 | throw new Error('error arguments', 'deepClone') 31 | } 32 | const targetObj = source.constructor === Array ? [] : {} 33 | Object.keys(source).forEach(keys => { 34 | if (source[keys] && typeof source[keys] === 'object') { 35 | targetObj[keys] = deepClone(source[keys]) 36 | } else { 37 | targetObj[keys] = source[keys] 38 | } 39 | }) 40 | return targetObj 41 | } 42 | 43 | module.exports = { 44 | param2Obj, 45 | deepClone 46 | } 47 | -------------------------------------------------------------------------------- /frontend/plop-templates/component/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if template}} 2 | 5 | {{/if}} 6 | 7 | {{#if script}} 8 | 20 | {{/if}} 21 | 22 | {{#if style}} 23 | 26 | {{/if}} 27 | -------------------------------------------------------------------------------- /frontend/plop-templates/component/prompt.js: -------------------------------------------------------------------------------- 1 | const { notEmpty } = require('../utils.js') 2 | 3 | module.exports = { 4 | description: 'generate vue component', 5 | prompts: [{ 6 | type: 'input', 7 | name: 'name', 8 | message: 'component name please', 9 | validate: notEmpty('name') 10 | }, 11 | { 12 | type: 'checkbox', 13 | name: 'blocks', 14 | message: 'Blocks:', 15 | choices: [{ 16 | name: '