Skip to content

Commit 6075c8b

Browse files
committed
Initial commit
0 parents  commit 6075c8b

36 files changed

+1416
-0
lines changed

backend/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-

backend/app/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-

backend/app/alembic.ini

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
script_location = alembic
6+
7+
# template used to generate migration files
8+
# file_template = %%(rev)s_%%(slug)s
9+
10+
# sys.path path, will be prepended to sys.path if present.
11+
# defaults to the current working directory.
12+
prepend_sys_path = .
13+
14+
# timezone to use when rendering the date within the migration file
15+
# as well as the filename.
16+
# If specified, requires the python-dateutil library that can be
17+
# installed by adding `alembic[tz]` to the pip requirements
18+
# string value is passed to dateutil.tz.gettz()
19+
# leave blank for localtime
20+
# timezone =
21+
22+
# max length of characters to apply to the
23+
# "slug" field
24+
# truncate_slug_length = 40
25+
26+
# set to 'true' to run the environment during
27+
# the 'revision' command, regardless of autogenerate
28+
# revision_environment = false
29+
30+
# set to 'true' to allow .pyc and .pyo files without
31+
# a source .py file to be detected as revisions in the
32+
# versions/ directory
33+
# sourceless = false
34+
35+
# version location specification; This defaults
36+
# to alembic/versions. When using multiple version
37+
# directories, initial revisions must be specified with --version-path.
38+
# The path separator used here should be the separator specified by "version_path_separator"
39+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
40+
41+
# version path separator; As mentioned above, this is the character used to split
42+
# version_locations. Valid values are:
43+
#
44+
# version_path_separator = :
45+
# version_path_separator = ;
46+
# version_path_separator = space
47+
version_path_separator = os # default: use os.pathsep
48+
49+
# the output encoding used when revision files
50+
# are written from script.py.mako
51+
# output_encoding = utf-8
52+
53+
;sqlalchemy.url = driver://user:pass@localhost/dbname
54+
55+
56+
[post_write_hooks]
57+
# post_write_hooks defines scripts or Python functions that are run
58+
# on newly generated revision scripts. See the documentation for further
59+
# detail and examples
60+
61+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
62+
# hooks = black
63+
# black.type = console_scripts
64+
# black.entrypoint = black
65+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
66+
67+
# Logging configuration
68+
[loggers]
69+
keys = root,sqlalchemy,alembic
70+
71+
[handlers]
72+
keys = console
73+
74+
[formatters]
75+
keys = generic
76+
77+
[logger_root]
78+
level = WARN
79+
handlers = console
80+
qualname =
81+
82+
[logger_sqlalchemy]
83+
level = WARN
84+
handlers =
85+
qualname = sqlalchemy.engine
86+
87+
[logger_alembic]
88+
level = INFO
89+
handlers =
90+
qualname = alembic
91+
92+
[handler_console]
93+
class = StreamHandler
94+
args = (sys.stderr,)
95+
level = NOTSET
96+
formatter = generic
97+
98+
[formatter_generic]
99+
format = %(levelname)-5.5s [%(name)s] %(message)s
100+
datefmt = %H:%M:%S

backend/app/alembic/README

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generic single-database configuration.

backend/app/alembic/env.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# -*- coding: utf-8 -*-
2+
import sys
3+
from logging.config import fileConfig
4+
5+
from sqlalchemy import engine_from_config
6+
from sqlalchemy import pool
7+
8+
from alembic import context
9+
10+
11+
sys.path.append('../../')
12+
# this is the Alembic Config object, which provides
13+
# access to the values within the .ini file in use.
14+
15+
16+
config = context.config
17+
18+
# Interpret the config file for Python logging.
19+
# This line sets up loggers basically.
20+
fileConfig(config.config_file_name)
21+
22+
# add your model's MetaData object here
23+
# for 'autogenerate' support
24+
# from myapp import mymodel
25+
# target_metadata = mymodel.Base.metadata
26+
27+
from backend.app.datebase.db_mysql import SQLALCHEMY_DATABASE_URL
28+
config.set_main_option('sqlalchemy.url', SQLALCHEMY_DATABASE_URL)
29+
30+
from backend.app.datebase.base import Base
31+
target_metadata = Base.metadata
32+
33+
34+
# other values from the config, defined by the needs of env.py,
35+
# can be acquired:
36+
# my_important_option = config.get_main_option("my_important_option")
37+
# ... etc.
38+
39+
40+
def run_migrations_offline():
41+
"""Run migrations in 'offline' mode.
42+
43+
This configures the context with just a URL
44+
and not an Engine, though an Engine is acceptable
45+
here as well. By skipping the Engine creation
46+
we don't even need a DBAPI to be available.
47+
48+
Calls to context.execute() here emit the given string to the
49+
script output.
50+
51+
"""
52+
url = config.get_main_option("sqlalchemy.url")
53+
context.configure(
54+
url=url,
55+
target_metadata=target_metadata,
56+
literal_binds=True,
57+
dialect_opts={"paramstyle": "named"},
58+
)
59+
60+
with context.begin_transaction():
61+
context.run_migrations()
62+
63+
64+
def run_migrations_online():
65+
"""Run migrations in 'online' mode.
66+
67+
In this scenario we need to create an Engine
68+
and associate a connection with the context.
69+
70+
"""
71+
connectable = engine_from_config(
72+
config.get_section(config.config_ini_section),
73+
prefix="sqlalchemy.",
74+
poolclass=pool.NullPool,
75+
)
76+
77+
with connectable.connect() as connection:
78+
context.configure(
79+
connection=connection, target_metadata=target_metadata
80+
)
81+
82+
with context.begin_transaction():
83+
context.run_migrations()
84+
85+
86+
if context.is_offline_mode():
87+
run_migrations_offline()
88+
else:
89+
run_migrations_online()

backend/app/alembic/script.py.mako

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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"}

backend/app/alembic/versions/none

Whitespace-only changes.

backend/app/api/__init__.py

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from fastapi import FastAPI
4+
5+
from backend.app.common.sys_redis import redis_client
6+
from backend.app.core.conf import settings
7+
from backend.app.api.v1 import v1
8+
from backend.app.middleware import register_middleware
9+
10+
11+
def register_app():
12+
# FastAPI
13+
app = FastAPI(
14+
title=settings.TITLE,
15+
version=settings.VERSION,
16+
description=settings.DESCRIPTION,
17+
docs_url=settings.DOCS_URL,
18+
redoc_url=settings.REDOCS_URL,
19+
openapi_url=settings.OPENAPI_URL
20+
)
21+
22+
# 中间件
23+
register_middleware(app)
24+
25+
# 路由
26+
register_router(app)
27+
28+
# 初始化连接
29+
register_init(app)
30+
31+
return app
32+
33+
34+
def register_router(app):
35+
"""
36+
路由
37+
:param app: FastAPI
38+
:return:
39+
"""
40+
app.include_router(
41+
v1,
42+
)
43+
44+
45+
def register_init(app):
46+
"""
47+
初始化连接
48+
:param app: FastAPI
49+
:return:
50+
"""
51+
52+
@app.on_event("startup")
53+
async def startup_event():
54+
# 连接redis
55+
await redis_client.init_redis_connect()
56+
57+
@app.on_event("shutdown")
58+
async def shutdown_event():
59+
await redis_client.init_redis_connect().close()

backend/app/api/jwt_security.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from datetime import datetime, timedelta
4+
from typing import Any, Optional, Union
5+
6+
from fastapi import Depends, HTTPException, status, Request
7+
from fastapi.security import OAuth2PasswordBearer
8+
from jose import jwt
9+
from passlib.context import CryptContext
10+
from pydantic import ValidationError
11+
from sqlalchemy.orm import Session
12+
13+
from backend.app.core.conf import settings
14+
from backend.app.crud import user_crud
15+
from backend.app.datebase.db_mysql import get_db
16+
from backend.app.model import User
17+
18+
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') # 密码加密
19+
20+
oauth2_schema = OAuth2PasswordBearer(tokenUrl='/v1/login') # 指明客户端请求token的地址
21+
22+
headers = {"WWW-Authenticate": "Bearer"} # 异常返回规范
23+
24+
25+
def get_hash_password(password: str) -> str:
26+
"""使用hash算法加密密码 """
27+
return pwd_context.hash(password)
28+
29+
30+
def verity_password(plain_password: str, hashed_password: str) -> bool:
31+
"""
32+
密码校验
33+
:param plain_password: 要验证的密码
34+
:param hashed_password: 要比较的hash密码
35+
:return: 比较密码之后的结果
36+
"""
37+
return pwd_context.verify(plain_password, hashed_password)
38+
39+
40+
def create_access_token(data: Union[int, Any], expires_delta: Optional[timedelta] = None) -> str:
41+
"""
42+
生成加密 token
43+
:param data: 传进来的值
44+
:param expires_delta: 增加的到期时间
45+
:return: 加密token
46+
"""
47+
if expires_delta:
48+
expires = datetime.utcnow() + expires_delta
49+
else:
50+
expires = datetime.utcnow() + timedelta(settings.ACCESS_TOKEN_EXPIRE_MINUTES)
51+
to_encode = {"exp": expires, "sub": str(data)}
52+
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, settings.ALGORITHM)
53+
return encoded_jwt
54+
55+
56+
async def get_current_user(db: Session = Depends(get_db), token: str = Depends(oauth2_schema)) -> User:
57+
"""
58+
通过token获取当前用户
59+
:param db:
60+
:param token:
61+
:return:
62+
"""
63+
credentials_exception = HTTPException(
64+
status.HTTP_401_UNAUTHORIZED,
65+
detail="无法验证凭据",
66+
headers={"WWW-Authenticate": "Bearer"},
67+
)
68+
try:
69+
# 解密token
70+
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
71+
user_id = payload.get('sub')
72+
if not user_id:
73+
raise credentials_exception
74+
except (jwt.JWTError, ValidationError):
75+
raise credentials_exception
76+
user = user_crud.get_user_by_id(db, user_id)
77+
return user
78+
79+
80+
async def get_current_is_superuser(user: User = Depends(get_current_user)) -> bool:
81+
"""
82+
通过token验证当前用户权限
83+
:param user:
84+
:return:
85+
"""
86+
is_superuser = user.is_superuser
87+
if not is_superuser:
88+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='用户权限不足', headers=headers)
89+
return is_superuser

backend/app/api/v1/__init__.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
from fastapi import APIRouter
4+
5+
from .test_redis import rd
6+
from .user import user
7+
8+
v1 = APIRouter(prefix='/v1')
9+
10+
v1.include_router(user, tags=['用户'])
11+
v1.include_router(rd, tags=['测试-Redis'])

0 commit comments

Comments
 (0)