├── Dockerfile ├── LICENSE ├── database.db ├── db.py ├── main.py ├── message.sql ├── models.py ├── readme.md ├── requirements.txt ├── schemas.py ├── static ├── Dockerfile ├── favicon.ico ├── message.html └── nginx.conf └── test_api.py /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7 2 | COPY . /app 3 | RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 4 | RUN echo 'Asia/Shanghai' >/etc/timezone 5 | WORKDIR ./app 6 | RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/ 7 | EXPOSE 80 8 | CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 zy7y 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 | -------------------------------------------------------------------------------- /database.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zy7y/sayhello/9ab6882581da57e0f5b2523849c5a1f42c5bb5d8/database.db -------------------------------------------------------------------------------- /db.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | 3 | from sqlalchemy.orm import sessionmaker 4 | 5 | engine = create_engine("sqlite:///database.db", connect_args={"check_same_thread": False}) 6 | 7 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 8 | 9 | session = SessionLocal() 10 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from sqlalchemy import func 3 | from starlette.middleware.cors import CORSMiddleware 4 | 5 | from schemas import * 6 | from db import session 7 | import models 8 | 9 | app = FastAPI(title="SayHello(留言板)", 10 | description=""" 11 | 翻自 《Flask Web开发实战_入门、进阶与原理解析(李辉著 )》 中的实战项目SayHello 12 | 原版Github: https://github.com/greyli/sayhello 13 | """ 14 | ) 15 | 16 | # 设置跨域 17 | app.add_middleware( 18 | CORSMiddleware, 19 | allow_origins=["*",], 20 | allow_credentials=True, 21 | allow_methods=["*"], 22 | allow_headers=["*"], 23 | ) 24 | 25 | 26 | @app.get("/index", name="欢迎首页") 27 | async def index(): 28 | return {"msg": "欢迎来到SayHello!"} 29 | 30 | 31 | @app.post("/message", name="添加留言", response_model=Response200) 32 | async def add_message(message: MessageCreate): 33 | message_obj = models.Message( 34 | name=message.name, 35 | body=message.body 36 | ) 37 | session.add(message_obj) 38 | session.commit() 39 | session.refresh(message_obj) 40 | return Response200(data=message_obj) 41 | 42 | 43 | @app.get("/message", name="分页获取留言列表", response_model=ResponseList200) 44 | async def get_messages(limit: int = 5, page: int = 1): 45 | # 统计条数 46 | total = session.query(func.count(models.Message.id)).scalar() 47 | skip = (page - 1) * limit # 计算当前页的起始数 48 | # 倒序显示 49 | data = session.query(models.Message).order_by(models.Message.create_at.desc()).offset(skip).limit(limit).all() 50 | return ResponseList200(total=total, data=data) 51 | -------------------------------------------------------------------------------- /message.sql: -------------------------------------------------------------------------------- 1 | -- 新建数据库,然后执行下面sql 2 | CREATE TABLE message( 3 | id PRIMARY KEY not null, 4 | name VARCHAR(60) not null, 5 | body VARCHAR(200) not null, 6 | create_at DATETIME 7 | ); -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy.ext.declarative import declarative_base 4 | from sqlalchemy import Column, String, DateTime, Integer 5 | 6 | # 得到默认Base基类 7 | Base = declarative_base() 8 | 9 | 10 | class Message(Base): 11 | __tablename__ = "message" 12 | id = Column(Integer, primary_key=True, index=True) 13 | name = Column(String(60), comment="昵称", nullable=False) 14 | body = Column(String(200), comment="内容", nullable=False) 15 | create_at = Column(DateTime, default=datetime.now, comment="创建时间") 16 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | > 翻自 《Flask Web开发实战_入门、进阶与原理解析(李辉著 )》 中的实战项目SayHello 3 | **线上体验地址:http://49.232.203.244:9001/message.html** 4 | 5 | # 图片加载不出来 6 | 所有图片都是用的gitee做图床,不知道为什么github展示不出来,需要看图片请前往 7 | [该项目的Gitee仓库地址](https://gitee.com/zy7y/sayhello) 8 | # 技术栈 9 | FastAPI + SQLAlchemy(sqlite3) + html + css + vue.js + axios 10 | # 动态 11 | 1. 新增留言, 留言列表接口, 接口测试 12 | 2. 完善前端页面,更改实时校验,https://blog.csdn.net/qq_22182989/article/details/103728781 13 | 3. 体验版部署,更新docker 部署文档 14 | # 本地启动 15 | 1. 项目目录下执行`pip install -r requirements.txt` 16 | 2. `Terminal(终端)`执行命令`uvicorn main:app --reload` 17 | 3. 访问服务 18 | - http://127.0.0.1:8000/docs # 接口文档 19 | 20 | # docker部署 21 | **详细内容请看:https://www.cnblogs.com/zy7y/p/14344375.html** 22 | 23 | ## 后端部署 24 | 1. 进入到项目目录下(命令请在命令行执行) 25 | 2. 执行`docker build -t sayhello .` 26 | 3. 运行容器`docker run -d --name sayhello-fastapi -p 8000:80 sayhello` 27 | 28 | ## 前端部署 29 | **需要确定static/message.html 中的 `baseURL`地址是不是后端服务器IP地址** 30 | ![](https://gitee.com/zy7y/blog_images/raw/master/img/20210129124621.png) 31 | 1. 进入到项目static目录下 32 | 2. 执行`docker build -t sayhello-front .` 33 | 3. 运行容器`docker run -d --name sayhello-front-9000 -p 9001:80 sayhello-front` 34 | 35 | ## 访问 IP:9001/message.html 36 | ![](https://gitee.com/zy7y/blog_images/raw/master/img/20210129124425.png) 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | atomicwrites==1.4.0 2 | attrs==20.3.0 3 | certifi==2020.12.5 4 | chardet==4.0.0 5 | click==7.1.2 6 | colorama==0.4.4 7 | fastapi==0.63.0 8 | h11==0.12.0 9 | idna==2.10 10 | iniconfig==1.1.1 11 | packaging==20.8 12 | pluggy==0.13.1 13 | py==1.10.0 14 | pydantic==1.7.3 15 | pyparsing==2.4.7 16 | pytest==6.2.2 17 | requests==2.25.1 18 | SQLAlchemy==1.3.22 19 | starlette==0.13.6 20 | toml==0.10.2 21 | urllib3==1.26.3 22 | uvicorn==0.13.3 23 | -------------------------------------------------------------------------------- /schemas.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from fastapi import Body 4 | from pydantic import BaseModel 5 | 6 | from typing import List 7 | 8 | 9 | class MessageBase(BaseModel): 10 | name: str = Body(..., min_length=2, max_length=8) 11 | body: str = Body(..., min_length=1, max_length=200) 12 | 13 | 14 | class MessageCreate(MessageBase): 15 | pass 16 | 17 | 18 | class Message(MessageBase): 19 | id: int = None 20 | create_at: datetime 21 | 22 | class Config: 23 | orm_mode = True 24 | 25 | 26 | class Response200(BaseModel): 27 | code: int = 200 28 | msg: str = "操作成功" 29 | data: Message = None 30 | 31 | 32 | class ResponseList200(Response200): 33 | total: int 34 | data: List[Message] 35 | 36 | 37 | class Response400(Response200): 38 | code: int = 400 39 | msg: str = "无数据返回" 40 | -------------------------------------------------------------------------------- /static/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.15.2-alpine 2 | COPY . /usr/share/nginx/html 3 | COPY nginx.conf /etc/nginx/conf.d/default.conf 4 | EXPOSE 80 5 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zy7y/sayhello/9ab6882581da57e0f5b2523849c5a1f42c5bb5d8/static/favicon.ico -------------------------------------------------------------------------------- /static/message.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Say Hello!(FastAPI) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | Say Hello 27 | 28 | to the world 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Submit 41 | Reset 42 | 43 | 44 |

{{messageTotal}} messages

45 |
46 |
47 |
48 | {{message.name}} 49 | 50 | #{{message.id}} 51 | 52 |
53 | 54 | {{message.create_at}} 55 | 56 |
57 |
58 |

{{message.body}}

59 |
60 |
61 | 62 | 63 | 64 | 66 | 67 |
68 | 69 | 70 |
71 | Copyright© 2021 /  72 |
73 |
74 | {{link.name}} /  75 |
76 | 77 |
78 |
79 |
80 | 81 | 207 | 208 | 365 | 366 | 367 | 368 | 369 | -------------------------------------------------------------------------------- /static/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | # gzip config 4 | gzip on; 5 | gzip_min_length 1k; 6 | gzip_comp_level 9; 7 | gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; 8 | gzip_vary on; 9 | gzip_disable "MSIE [1-6]\."; 10 | 11 | root /usr/share/nginx/html; 12 | 13 | location / { 14 | try_files $uri $uri/ /message.html /index.html; 15 | } 16 | location /api { 17 | proxy_pass https://preview.pro.ant.design; 18 | proxy_set_header X-Forwarded-Proto $scheme; 19 | proxy_set_header Host $http_host; 20 | proxy_set_header X-Real-IP $remote_addr; 21 | } 22 | } -------------------------------------------------------------------------------- /test_api.py: -------------------------------------------------------------------------------- 1 | # 测试API 2 | import operator 3 | 4 | import pytest 5 | from fastapi.testclient import TestClient 6 | from sqlalchemy import text 7 | 8 | from main import app, session 9 | 10 | client = TestClient(app) 11 | 12 | 13 | def test_index(): 14 | response = client.get("/index") 15 | assert response.status_code == 200 16 | assert response.json() == {"msg": "欢迎来到SayHello!"} 17 | 18 | 19 | @pytest.mark.parametrize("skip, limit", [[1, 2], [1, 10], [-1, 5]]) 20 | def test_get_message(skip, limit): 21 | response = client.get("/message", params={"skip": skip, "limit": limit}) 22 | assert response.status_code == 200 23 | sql = "select * from message order by create_at desc limit :skip,:limit" 24 | data = session.execute(text(sql), {"skip": skip, "limit": limit}).fetchall() 25 | assert response.json()['data'][0]["id"] == data[0]["id"] 26 | 27 | 28 | @pytest.mark.parametrize("data", [{"name": "七七", "body": "回踩!"}]) 29 | def test_add_message(data): 30 | response = client.post("/message", json=data) 31 | assert response.status_code == 200 32 | sql = "select * from message where name = :name" 33 | result = session.execute(text(sql), {"name": data["name"]}).fetchall() 34 | assert result is not None 35 | --------------------------------------------------------------------------------