Commit 6d1e3dae authored by Amy Yang's avatar Amy Yang

1 后端重构

2 前端使用antd搭建项目
parent abee8e7e
## vue-demo
vue + vuefity 前端项目
## vue-antd-demo
vue + antd 前端项目
## backend-demo
简单的后端项目
## backend-router-demo
- 使用APIRouter的项目
- 结构层次清晰
\ No newline at end of file
from sqlalchemy.orm import Session
import hashlib
from . import models, schemas
def get_user(db: Session, user_id: int):
......@@ -11,8 +12,10 @@ def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
md5 = hashlib.md5()
md5.update(user.password.encode('utf-8'))
passwordMD5 = md5.hexdigest()
db_user = models.User(email=user.email, password=passwordMD5)
db.add(db_user)
db.commit()
db.refresh(db_user)
......
from typing import List, Optional
from fastapi import Depends, FastAPI, HTTPException
from fastapi import Depends, FastAPI, Form, HTTPException
from sqlalchemy.orm import Session
import hashlib
from . import crud, models, schemas
from .database import SessionLocal, engine
......@@ -16,6 +17,22 @@ def get_db():
finally:
db.close()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.post("/login/", response_model=schemas.User)
async def login(email: str = Form(...), password: str = Form(...), db:Session = Depends(get_db)):
user = crud.get_user_by_email(db, email=email)
if user is None:
raise HTTPException(status_code=400, detail="Email not found")
md5 = hashlib.md5()
md5.update(password.encode('utf-8'))
passwordMD5 = md5.hexdigest()
if passwordMD5 != user.password:
raise HTTPException(status_code=400, detail="Email or password is wrong")
return user
@app.post("/users", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db:Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
......
......@@ -7,7 +7,7 @@ class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
password = Column(String)
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
......
## 数据库信息
postgresql://postgres:1@localhost:5432/antdemo
//SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
## 启动项目
uvicorn api.main:app --reload
## 访问swagger
http://127.0.0.1:8000/docs#/
\ No newline at end of file
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = postgresql://postgres:1@localhost:5432/antdemo
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks=black
# black.type=console_scripts
# black.entrypoint=black
# black.options=-l 79
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
Generic single-database configuration.
\ No newline at end of file
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import sys
from os.path import abspath, dirname
sys.path.append(dirname(dirname(abspath(__file__)))) # 将项目路径引入
from api.models import Base
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
"""message
Revision ID: ac95fea8c6b2
Revises:
Create Date: 2021-04-08 13:38:44.841600
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ac95fea8c6b2'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email', sa.String(), nullable=True),
sa.Column('hashed_password', sa.String(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False)
op.create_table('item',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('owner_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_item_description'), 'item', ['description'], unique=False)
op.create_index(op.f('ix_item_id'), 'item', ['id'], unique=False)
op.create_index(op.f('ix_item_title'), 'item', ['title'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_item_title'), table_name='item')
op.drop_index(op.f('ix_item_id'), table_name='item')
op.drop_index(op.f('ix_item_description'), table_name='item')
op.drop_table('item')
op.drop_index(op.f('ix_user_id'), table_name='user')
op.drop_index(op.f('ix_user_email'), table_name='user')
op.drop_table('user')
# ### end Alembic commands ###
from sqlalchemy.orm import Session
import hashlib
from .. import models, schemas
def get_items(db:Session, skip: int = 0, limit: int = 100):
return db.query(models.items.Item).offset(skip).limit(limit).all()
def create_user_items(db: Session, item: schemas.items.ItemCreate, user_id: int):
db_item = models.items.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
\ No newline at end of file
from sqlalchemy.orm import Session
import hashlib
from .. import models, schemas
def get_user(db: Session, user_id: int):
return db.query(models.users.User).filter(models.users.User.id == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.users.User).filter(models.users.User.email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.users.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.users.UserCreate):
md5 = hashlib.md5()
md5.update(user.password.encode('utf-8'))
passwordMD5 = md5.hexdigest()
db_user = models.users.User(email=user.email, password=passwordMD5)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def delete_user(db:Session, user_id: int):
count = db.query(models.users.User).filter(models.users.User.id == user_id).delete()
db.commit()
return count
def modify_user(db:Session, user_id: int, user: schemas.users.User):
count = db.query(models.users.User).filter(models.users.User.id == user_id).update(user)
db.commit()
return count
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:1@localhost:5432/antdemo"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
\ No newline at end of file
# 依赖项
# 简单的依赖项来读取一个自定义的 X-Token 请求首部
from fastapi import Header, HTTPException
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def get_query_token(token: str):
if token != "jessica":
raise HTTPException(status_code=400, detail="No Jessica token provided")
\ No newline at end of file
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
\ No newline at end of file
from fastapi import Depends, FastAPI
# from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users
from . import models
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
# app = FastAPI(dependencies=[Depends(get_query_token)])
app = FastAPI()
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"]
)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
\ No newline at end of file
from . import users, items
from ..database import Base
from ..database import SessionLocal, engine
\ No newline at end of file
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from ..database import Base
class Item(Base):
__tablename__ = "item"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("user.id"))
owner = relationship("User", back_populates="items")
\ No newline at end of file
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from ..database import Base
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
password = Column(String)
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
\ No newline at end of file
from . import users, items
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException
from .. import models, schemas
from ..dependencies import get_token_header
from sqlalchemy.orm import Session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}}
)
@router.get("/items", tags=["items"], response_model=schemas.items.Item)
def get_items(db:Session= Depends(get_db), skip: int = 0, limit: int = 100):
return db.query(models.items.Item).offset(skip).limit(limit).all()
@router.post("/items", tags=["items"], response_model=schemas.items.Item)
def create_user_items(item: schemas.items.ItemCreate, user_id: int, db: Session= Depends(get_db)):
db_item = models.items.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
\ No newline at end of file
from fastapi import APIRouter, Depends, HTTPException
from .. import models, schemas
from ..database import SessionLocal, engine
from sqlalchemy.orm import Session
from ..crud import users
import hashlib
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
router = APIRouter()
# 根据user id查询用户
@router.get("/users/{user_id}", tags=["users"], response_model=schemas.users.User)
async def get_user(user_id: int, db: Session = Depends(get_db)):
user = users.get_user(db, user_id)
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
# 查询用户列表
@router.get("/users", tags=["users"], response_model=schemas.users.User)
async def get_users(skip: int = 0, limit: int = 100, db: Session= Depends(get_db)):
users = users.get_users(db, skip=skip, limit=limit)
return users
# 新建用户
@router.post("/users", tags=["users"], response_model=schemas.users.User)
def create_user(user: schemas.users.UserCreate, db: Session= Depends(get_db)):
db_user = users.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return users.create_user(db, user)
# 删除用户
@router.delete("/users/{user_id}", response_model={})
def delete_user(user_id: int, db: Session = Depends(get_db)):
del_count = users.delete_user(db, user_id)
return {"count": del_count}
# 修改用户(有问题)
@router.put("/users/{user_id}", response_model={})
def modify_user(user_id:int, user: schemas.users.UserCreate, db: Session= Depends(get_db)):
count = users.modify_user(db, user_id, user)
return {"count": count}
# 用户登录
@router.post("/login", response_model=schemas.users.User)
def login(email: str,password:str, db: Session= Depends(get_db)):
db_user = users.get_user_by_email(db, email=email)
if db_user is None:
raise HTTPException(status_code=400, detail="Email not registered")
md5 = hashlib.md5()
md5.update(password.encode('utf-8'))
passwordMD5 = md5.hexdigest()
if passwordMD5 != db_user.password:
raise HTTPException(status_code=404, detail="Email or Password is error")
return db_user
\ No newline at end of file
from . import users, items
\ No newline at end of file
from typing import List, Optional
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: Optional[str] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
\ No newline at end of file
from typing import List, Optional
from pydantic import BaseModel
from .items import Item
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
......@@ -2536,6 +2536,21 @@
"integrity": "sha1-1h9G2DslGSUOJ4Ta9bCUeai0HFk=",
"dev": true
},
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.14.0"
},
"dependencies": {
"follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
}
}
},
"babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npm.taobao.org/babel-eslint/download/babel-eslint-10.1.0.tgz",
......@@ -7266,6 +7281,11 @@
"launch-editor": "^2.2.1"
}
},
"leaflet": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz",
"integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw=="
},
"less": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz",
......
......@@ -9,8 +9,10 @@
},
"dependencies": {
"ant-design-vue": "^1.7.4",
"axios": "^0.21.1",
"babel-plugin-import": "^1.13.3",
"core-js": "^3.6.5",
"leaflet": "^1.7.1",
"vue": "^2.6.11",
"vue-i18n": "^8.24.2",
"vue-router": "^3.2.0",
......
......@@ -2,11 +2,14 @@ import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import utils from "./utils";
import "./core/antd";
import i18n from "./assets/i18n";
import "ant-design-vue/dist/antd.less";
Vue.config.productionTip = false;
Vue.prototype.$utils = utils;
new Vue({
router,
store,
......
......@@ -13,6 +13,11 @@ const routes = [
path: "/login",
name: "Login",
component: () => import("../views/auth/Login.vue")
},
{
path: "/map",
name: "Map",
component: () => import("../views/map/Map.vue")
}
];
......
import map from "./map";
export default {
map
};
// utils/map.js
import "leaflet/dist/leaflet.css";
import $L from "leaflet";
const createMap = (divId, options) => {
let map = $L.map(divId, options);
return map;
};
const createTileLayer = async (map, url, options) => {
let tileLayer = await $L.tileLayer(url, options);
tileLayer.addTo(map);
return tileLayer;
};
export default { createMap, createTileLayer };
import axios from "axios";
import notification from "ant-design-vue/es/notification";
const service = axios.create({
timeout: 6000
});
service.interceptors.request.use(
config => config,
error => {
return Promise.reject(error);
}
);
service.interceptors.response.use(
response => {
const res = response.data;
return res;
},
async error => {
const { data, statusText } = error.response;
let m = { message: statusText };
if (data && data.detail) {
if (typeof data.detail === "string") {
m.description = data.detail;
} else {
m.description = JSON.stringify(data.detail).replace(
/"([^"]+)":/g,
"$1:"
);
}
}
notification.error(m);
return Promise.reject(error);
}
);
export default service;
......@@ -24,7 +24,7 @@
</a-input>
</a-form-item>
<a-form-item>
<a-input
<a-input-password
:placeholder="$t('password')"
v-decorator="[
'password',
......@@ -32,7 +32,7 @@
]"
>
<a-icon slot="prefix" type="lock" style="color:rgba(0,0,0,.25)" />
</a-input>
</a-input-password>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit" block>{{
......@@ -59,6 +59,7 @@
</template>
<script>
import request from "@/utils/request";
const languageLst = [
{
name: "en",
......@@ -82,9 +83,19 @@ export default {
},
login(e) {
e.preventDefault();
this.form.validateFields((err, values) => {
const {
form: { validateFields }
} = this;
validateFields((err, values) => {
if (!err) {
console.log(values);
return request({
url: "/login",
params: { email: values["username"], password: values["password"] },
method: "post"
}).then(() => {
this.$router.push({ path: "/map" });
});
}
});
}
......
// src/views/Map.Vue
<template>
<div class="map-container" id="map-container"></div>
</template>
<script>
export default {
name: "mapView",
components: {},
data() {
return {
map: null,
OSMUrl: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
};
},
mounted() {
this.map = this.$utils.map.createMap("map-container");
// 加载 open street map 图层服务
this.$utils.map.createTileLayer(this.map, this.OSMUrl, {});
// 设施地图视图 中心位置
this.map.setView([39.915, 116.404], 5);
}
};
</script>
<style lang="less">
.map-container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
</style>
......@@ -10,5 +10,9 @@ module.exports = {
javascriptEnabled: true
}
}
},
devServer: {
proxy: "http://127.0.0.1:8000/"
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment