Parcourir la source

init:项目初始化

yangxiaolong il y a 1 mois
Parent
commit
1e1d132897

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+__pycache__/
+*.py[cod]
+.idea/
+.venv/

+ 0 - 10
.idea/.gitignore

@@ -1,10 +0,0 @@
-# 默认忽略的文件
-/shelf/
-/workspace.xml
-# 已忽略包含查询文件的默认文件夹
-/queries/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# 基于编辑器的 HTTP 客户端请求
-/httpRequests/

+ 0 - 8
.idea/FastAPI.iml

@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="PYTHON_MODULE" version="4">
-  <component name="NewModuleRootManager">
-    <content url="file://$MODULE_DIR$" />
-    <orderEntry type="jdk" jdkName="Python 3.13 (base)" jdkType="Python SDK" />
-    <orderEntry type="sourceFolder" forTests="false" />
-  </component>
-</module>

+ 0 - 6
.idea/inspectionProfiles/profiles_settings.xml

@@ -1,6 +0,0 @@
-<component name="InspectionProjectProfileManager">
-  <settings>
-    <option name="USE_PROJECT_PROFILE" value="false" />
-    <version value="1.0" />
-  </settings>
-</component>

+ 0 - 7
.idea/misc.xml

@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="Black">
-    <option name="sdkName" value="Python 3.13 (base)" />
-  </component>
-  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (base)" project-jdk-type="Python SDK" />
-</project>

+ 0 - 8
.idea/modules.xml

@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="ProjectModuleManager">
-    <modules>
-      <module fileurl="file://$PROJECT_DIR$/.idea/FastAPI.iml" filepath="$PROJECT_DIR$/.idea/FastAPI.iml" />
-    </modules>
-  </component>
-</project>

+ 0 - 6
.idea/vcs.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$" vcs="Git" />
-  </component>
-</project>

+ 0 - 109
1fastapi.py

@@ -1,109 +0,0 @@
-# 导入FastAPI静态文件挂载模块,用于托管图片、css、js等静态资源
-from fastapi.staticfiles import StaticFiles
-from fastapi.templating import Jinja2Templates
-from tortoise.contrib.fastapi import register_tortoise
-from settings import TORTOISE_ORM
-# 导入FastAPI核心实例、Request请求对象(用于接收请求参数)
-from fastapi import FastAPI, Request
-from fastapi.middleware.cors import CORSMiddleware
-
-
-# 导入uvicorn ASGI异步web服务器,用来运行FastAPI项目
-import uvicorn
-
-# 从api子包中导入拆分后的各个路由蓝图对象,实现接口模块化拆分
-from api.book import api_book
-from api.cbs import api_cbs
-from api.zz import api_zz
-
-
-
-# 实例化FastAPI应用程序核心对象
-app = FastAPI()
-
-app.add_middleware(
-    CORSMiddleware,
-    # allow_origins=origins,  # *: 代表所有客户端
-    allow_origins=["*"],  # *: 代表所有客户端
-    allow_credentials=True,
-    allow_methods=["GET"],
-    allow_headers=["*"],
-)
-
-register_tortoise(
-    app=app,
-    config=TORTOISE_ORM,
-)
-
-# 挂载静态资源目录
-# path:浏览器访问URL前缀;directory:本地存放静态文件的文件夹;name:内部标识名
-# 访问示例:http://127.0.0.1:8000/upimg/img.png 即可直接读取upimg文件夹下的img.png图片
-app.mount("/upimg", StaticFiles(directory="upimg"), name="upimg")
-
-templates = Jinja2Templates(directory="templates")
-
-# 批量注册拆分后的子路由,统一添加接口前缀和接口分类标签(用于自动接口文档分组)
-# prefix:给当前分组所有接口统一拼接URL前缀;tags:接口文档里的分组名称
-app.include_router(api_book, prefix="/book", tags=["book interface"])
-app.include_router(api_cbs, prefix="/cbs", tags=["cbs interface"])
-app.include_router(api_zz, prefix="/zz", tags=["zz interface"])
-
-
-# 根路径访问接口,项目首页测试接口
-@app.get("/")
-async def root():
-    return {"message": "Hello World"}
-
-
-# GET请求测试接口,演示基础GET方式请求
-@app.get("/get")
-async def get_test():
-    return {"method": "get method"}
-
-
-# POST请求测试接口,演示基础POST方式请求
-@app.post("/post")
-async def post_test():
-    return {"method": "post method"}
-
-
-# PUT请求测试接口,常用于更新资源操作
-@app.put("/put")
-async def put_test():
-    return {"method": "put method"}
-
-
-# DELETE请求测试接口,常用于删除资源操作
-@app.delete("/delete")
-async def delete_test():
-    return {"method": "delete method"}
-
-
-# GET接口:获取URL拼接的Query查询参数
-# Request对象接收完整请求对象,通过query_params提取地址栏?后的参数
-@app.get("/get_test")
-async def get_test(request: Request):
-    get_test = request.query_params
-    print(get_test)  # 在服务端控制台打印GET携带的查询参数
-    return {"method": "get_test"}
-
-
-# POST接口:接收JSON格式请求体数据
-# await request.json() 异步读取请求体中的json数据,必须加await否则拿不到真实数据
-@app.post("/post_test")
-async def post_test(request: Request):
-    post_test = await request.json()
-    print(post_test)  # 控制台打印前端传递的json请求体内容
-    return {"method": "post_test"}
-
-@app.get("/jinjia2templates")
-async def jinjia2templates(request: Request):
-    return templates.TemplateResponse(
-        request=request,
-        name="index.html",
-        context={
-            "request": request,
-            "books": ["平凡的世界","活着","兄弟","围城"]
-        }
-    )
-

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# 电影管理 API
+
+场景包含电影、制片公司和演员。一部电影属于一家制片公司,并可以关联
+多位演员。
+
+## 启动
+
+```powershell
+uv pip install -r requirements.txt
+uv run uvicorn main:app --reload
+```
+
+启动后访问 `http://127.0.0.1:8000/docs` 查看接口文档。
+
+## 接口
+
+| 请求 | 地址 | 方法名 |
+| --- | --- | --- |
+| GET | `/movies` | `list_movies` |
+| POST | `/movies` | `create_movie` |
+| PUT | `/movies/{movie_id}` | `update_movie` |
+| DELETE | `/movies/{movie_id}` | `delete_movie` |
+| GET | `/studios` | `list_studios` |
+| POST | `/studios` | `create_studio` |
+| PUT | `/studios/{studio_id}` | `update_studio` |
+| DELETE | `/studios/{studio_id}` | `delete_studio` |
+| GET | `/actors` | `list_actors` |
+| POST | `/actors` | `create_actor` |
+| PUT | `/actors/{actor_id}` | `update_actor` |
+| DELETE | `/actors/{actor_id}` | `delete_actor` |
+
+所有 JSON 接口统一返回 `code`、`message` 和 `data` 三个字段。电影模板页面
+地址为 `/pages/movies`。

+ 39 - 0
api/actors.py

@@ -0,0 +1,39 @@
+from fastapi import APIRouter
+
+from api.responses import success_response
+
+
+router = APIRouter()
+
+
+@router.get("")
+async def list_actors():
+    return success_response(
+        data={"method": "GET"},
+        message="获取演员列表成功",
+    )
+
+
+@router.post("", status_code=201)
+async def create_actor():
+    return success_response(
+        data={"method": "POST"},
+        message="创建演员成功",
+        code=201,
+    )
+
+
+@router.put("/{actor_id}")
+async def update_actor(actor_id: int):
+    return success_response(
+        data={"id": actor_id, "method": "PUT"},
+        message="更新演员成功",
+    )
+
+
+@router.delete("/{actor_id}")
+async def delete_actor(actor_id: int):
+    return success_response(
+        data={"id": actor_id, "method": "DELETE"},
+        message="删除演员成功",
+    )

+ 0 - 18
api/book.py

@@ -1,18 +0,0 @@
-from fastapi import APIRouter
-
-api_book = APIRouter()
-
-@api_book.get("/get")
-async def get_test():
-    return {"method": "get method"}
-
-@api_book.post("/post")
-async def post_test():
-    return {"method": "post method"}
-@api_book.put("/put")
-async def put_test():
-    return {"method": "put method"}
-
-@api_book.delete("/delete")
-async def delete_test():
-    return {"method": "delete method"}

+ 0 - 39
api/cbs.py

@@ -1,39 +0,0 @@
-from fastapi import APIRouter
-from models import Publish
-
-api_cbs = APIRouter()
-
-@api_cbs.get("/")
-async def getAllPublish():
-    # 这是 list 对象
-    publish_obj = await Publish.all()
-    print(publish_obj)
-    # 这是 queryset 对象
-    # publish_obj = Publish.all()
-
-    # filter
-    # users = await User.filter(name=name).all()
-
-    # get
-    # user = await User.get(id=user_id)
-
-    # order_by
-    # users = await User.all().order_by('name')
-
-    # limit
-    # users = await User.all().limit(limit)
-
-    # print(publish_obj)
-    # return {'code': 200, 'data': publish_obj}
-    return {"message": "Hello cbs"}
-
-@api_cbs.post("/post")
-async def post_test():
-    return {"method": "post method"}
-@api_cbs.put("/put")
-async def put_test():
-    return {"method": "put method"}
-
-@api_cbs.delete("/delete")
-async def delete_test():
-    return {"method": "delete method"}

+ 39 - 0
api/movies.py

@@ -0,0 +1,39 @@
+from fastapi import APIRouter
+
+from api.responses import success_response
+
+
+router = APIRouter()
+
+
+@router.get("")
+async def list_movies():
+    return success_response(
+        data={"method": "GET"},
+        message="获取电影列表成功",
+    )
+
+
+@router.post("", status_code=201)
+async def create_movie():
+    return success_response(
+        data={"method": "POST"},
+        message="创建电影成功",
+        code=201,
+    )
+
+
+@router.put("/{movie_id}")
+async def update_movie(movie_id: int):
+    return success_response(
+        data={"id": movie_id, "method": "PUT"},
+        message="更新电影成功",
+    )
+
+
+@router.delete("/{movie_id}")
+async def delete_movie(movie_id: int):
+    return success_response(
+        data={"id": movie_id, "method": "DELETE"},
+        message="删除电影成功",
+    )

+ 14 - 0
api/responses.py

@@ -0,0 +1,14 @@
+from typing import Any
+
+
+def success_response(
+    data: Any = None,
+    message: str = "success",
+    code: int = 200,
+) -> dict[str, Any]:
+    """生成统一的成功响应。"""
+    return {
+        "code": code,
+        "message": message,
+        "data": data,
+    }

+ 41 - 0
api/studios.py

@@ -0,0 +1,41 @@
+from fastapi import APIRouter
+
+from api.responses import success_response
+from models import Studio
+
+
+router = APIRouter()
+
+
+@router.get("")
+async def list_studios():
+    studios = await Studio.all().values("id", "name", "contact_email")
+    return success_response(
+        data=studios,
+        message="获取制片公司列表成功",
+    )
+
+
+@router.post("", status_code=201)
+async def create_studio():
+    return success_response(
+        data={"method": "POST"},
+        message="创建制片公司成功",
+        code=201,
+    )
+
+
+@router.put("/{studio_id}")
+async def update_studio(studio_id: int):
+    return success_response(
+        data={"id": studio_id, "method": "PUT"},
+        message="更新制片公司成功",
+    )
+
+
+@router.delete("/{studio_id}")
+async def delete_studio(studio_id: int):
+    return success_response(
+        data={"id": studio_id, "method": "DELETE"},
+        message="删除制片公司成功",
+    )

+ 0 - 18
api/zz.py

@@ -1,18 +0,0 @@
-from fastapi import APIRouter
-
-api_zz = APIRouter()
-
-@api_zz.get("/get")
-async def get_test():
-    return {"method": "get method"}
-
-@api_zz.post("/post")
-async def post_test():
-    return {"method": "post method"}
-@api_zz.put("/put")
-async def put_test():
-    return {"method": "put method"}
-
-@api_zz.delete("/delete")
-async def delete_test():
-    return {"method": "delete method"}

+ 68 - 0
main.py

@@ -0,0 +1,68 @@
+from pathlib import Path
+
+from fastapi import FastAPI, Request
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+from tortoise.contrib.fastapi import register_tortoise
+
+from api.actors import router as actors_router
+from api.movies import router as movies_router
+from api.responses import success_response
+from api.studios import router as studios_router
+from settings import TORTOISE_ORM
+
+
+BASE_DIR = Path(__file__).resolve().parent
+
+app = FastAPI(title="电影管理 API")
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],
+    allow_credentials=False,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+register_tortoise(app=app, config=TORTOISE_ORM)
+
+app.mount(
+    "/static",
+    StaticFiles(directory=BASE_DIR / "upimg"),
+    name="static",
+)
+templates = Jinja2Templates(directory=BASE_DIR / "templates")
+
+app.include_router(movies_router, prefix="/movies", tags=["电影"])
+app.include_router(studios_router, prefix="/studios", tags=["制片公司"])
+app.include_router(actors_router, prefix="/actors", tags=["演员"])
+
+
+@app.get("/")
+async def get_home():
+    return success_response(
+        data={"docs": "/docs"},
+        message="电影管理 API 运行正常",
+    )
+
+
+@app.get("/demo/query")
+async def get_query_parameters(request: Request):
+    return success_response(data=dict(request.query_params))
+
+
+@app.post("/demo/body")
+async def get_request_body(request: Request):
+    return success_response(data=await request.json())
+
+
+@app.get("/pages/movies")
+async def render_movies_page(request: Request):
+    return templates.TemplateResponse(
+        request=request,
+        name="index.html",
+        context={
+            "movies": ["肖申克的救赎", "霸王别姬", "星际穿越", "千与千寻"],
+        },
+    )

+ 0 - 67
migrations/models/0_20260615184211_init.py

@@ -1,67 +0,0 @@
-from tortoise import BaseDBAsyncClient
-
-RUN_IN_TRANSACTION = True
-
-
-async def upgrade(db: BaseDBAsyncClient) -> str:
-    return """
-        CREATE TABLE IF NOT EXISTS `author` (
-    `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
-    `name` VARCHAR(32) NOT NULL,
-    `age` INT NOT NULL
-) CHARACTER SET utf8mb4;
-CREATE TABLE IF NOT EXISTS `publish` (
-    `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
-    `name` VARCHAR(32) NOT NULL,
-    `email` VARCHAR(32) NOT NULL
-) CHARACTER SET utf8mb4;
-CREATE TABLE IF NOT EXISTS `book` (
-    `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
-    `title` VARCHAR(32) NOT NULL,
-    `price` INT NOT NULL,
-    `img_url` VARCHAR(255),
-    `bread` INT NOT NULL,
-    `bcomment` INT NOT NULL,
-    `publishs_id` INT NOT NULL,
-    CONSTRAINT `fk_book_publish_8999eb74` FOREIGN KEY (`publishs_id`) REFERENCES `publish` (`id`) ON DELETE CASCADE
-) CHARACTER SET utf8mb4;
-CREATE TABLE IF NOT EXISTS `aerich` (
-    `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
-    `version` VARCHAR(255) NOT NULL,
-    `app` VARCHAR(100) NOT NULL,
-    `content` JSON NOT NULL
-) CHARACTER SET utf8mb4;
-CREATE TABLE IF NOT EXISTS `book_author` (
-    `book_id` INT NOT NULL,
-    `author_id` INT NOT NULL,
-    FOREIGN KEY (`book_id`) REFERENCES `book` (`id`) ON DELETE CASCADE,
-    FOREIGN KEY (`author_id`) REFERENCES `author` (`id`) ON DELETE CASCADE,
-    UNIQUE KEY `uidx_book_author_book_id_818352` (`book_id`, `author_id`)
-) CHARACTER SET utf8mb4 COMMENT='作者';"""
-
-
-async def downgrade(db: BaseDBAsyncClient) -> str:
-    return """
-        """
-
-
-MODELS_STATE = (
-    "eJztmW1P2zAQgP8K6icmdVMp7UD7lnZDY4wXAZsmAYqcxE2tOnZwnEGF8t9nOy9OkzQ05a"
-    "2s/VLa813se3K+85mHlkcdiINPRsjHlLW+bD20CPCg+FIYaW+1gO9ruRRwYGGlCrSOFXAG"
-    "bC6kI4ADKEQODGyGfI4oEVISYiyF1BaKiLhaFBJ0G0KTUxfyMZRruboRYkQceA+D9Kc/MU"
-    "cIYmdmqciRcyu5yae+kh0SfqAU5WyWaVMcekQr+1OxZJJpI8Kl1IUEMsChfDxnoVy+XF3i"
-    "Z+pRvFKtEi8xZ+PAEQgxz7m7IAObEslPrCZQDrpylo/dnd5eb3/3c29fqKiVZJK9KHZP+x"
-    "4bKgInl61IjQMOYg2FUXNTf0vkhmPAqtGl+gV4YslFeCmqOnqpQOPTIfNM/Dxwb2JIXD4W"
-    "P3e7NbB+G+fD78b59m73g/SFiiCOY/skGemqIclT8wNuBb65gZdoPx55KwLvGYJP7tjRJB"
-    "d7UmABe3IHmGOWRmiXztMtD3ldrzKoLUonQfm1HAMyvaTyU72dQ+ErIHbVG0kS30A8Z5k3"
-    "s2RSaF2HvVHfvg73O51+a6E3FKURlkr1ZMoHs5DKU48YxNKfbDhO3zEzyhTuCZymLM04YW"
-    "ZvIhmKjZJBPmY0dMeZiS4IgquYG/I4sxgXQ+PrNyk3i6lWxYoHiNglTuJx1J5dekV1Sl2a"
-    "X5usVGNTmVYsOdRVJo44blSaMoNNbYoJ+gzZTapTpr9O9SkPDHmuGTLcJOhyJkuFXbIp3y"
-    "zquv3+AmEntObGnRqb5WgxCJpkvUx/XQPPsqnnwdjbRZnlTNYVmx9aGAXjwGxUYwtW6wSv"
-    "dBgvsyyDPKAMIpccwUWPzWfxo1YTY82JmYG77PxWjJLKQ2z0Cr1M/mT+tG5GX+P8P/2M9q"
-    "nY0egecLafmWlaih1Nrtl5Yj+jGNY3NOk2qehpcjtoflvj55Q2nc2KZdr25s7txfoa6AHU"
-    "6JCeGawzwQYXcY/epA0Ss4Ojc5l2pXdzy86yl2hvdxSInli8a7O+AUWXXZn0k5HanA+0zi"
-    "blv6OU/xeyINkliyatnMn7TFsvcrUgt0YDiIn6+wS40+ksAFBozQWoxmYBihl55U3Dj4vT"
-    "k2qIOZMCyF9EOHjlIJu3t8RZlN+sJtYaitJruWgvCG5xHt72sfGnyHX483SgKNCAu0w9RT"
-    "1g8Kr/56ooL9E/QZ4swQ=="
-)

+ 47 - 14
models.py

@@ -2,22 +2,55 @@ from tortoise.models import Model
 from tortoise import fields
 
 
-class Publish(Model):
-    name = fields.CharField(max_length=32, verbose_name="出版社名称")
-    email = fields.CharField(max_length=32, verbose_name="出版社邮箱")
+class Studio(Model):
+    name = fields.CharField(max_length=32, verbose_name="制片公司名称")
+    contact_email = fields.CharField(
+        max_length=32,
+        source_field="email",
+        verbose_name="联系邮箱",
+    )
 
+    class Meta:
+        # 沿用已有数据库表,场景改名不会删除原来的数据。
+        table = "publish"
 
-class Author(Model):
-    name = fields.CharField(max_length=32, verbose_name="作者")
+
+class Actor(Model):
+    name = fields.CharField(max_length=32, verbose_name="演员")
     age = fields.IntField(verbose_name="年龄")
 
+    class Meta:
+        table = "author"
+
+
+class Movie(Model):
+    name = fields.CharField(
+        max_length=32,
+        source_field="title",
+        verbose_name="电影名称",
+    )
+    budget = fields.IntField(source_field="price", verbose_name="制作预算")
+    poster_url = fields.CharField(
+        max_length=255,
+        null=True,
+        source_field="img_url",
+        verbose_name="海报地址",
+    )
+    views = fields.IntField(source_field="bread", verbose_name="播放量")
+    comments = fields.IntField(source_field="bcomment", verbose_name="评论量")
+    studio = fields.ForeignKeyField(
+        "models.Studio",
+        related_name="movies",
+        source_field="publishs_id",
+    )
+    actors = fields.ManyToManyField(
+        "models.Actor",
+        related_name="movies",
+        through="book_author",
+        forward_key="author_id",
+        backward_key="book_id",
+        description="参演演员",
+    )
 
-class Book(Model):
-    title = fields.CharField(max_length=32, verbose_name="书籍名称")
-    price = fields.IntField(verbose_name="价格")
-    # pub_date = models.DateField(verbose_name="出版日期")
-    img_url = fields.CharField(max_length=255, null=True, blank=True, verbose_name="")
-    bread = fields.IntField(verbose_name="阅读量")
-    bcomment = fields.IntField(verbose_name="评论量")
-    publishs = fields.ForeignKeyField('models.Publish', related_name='books')
-    authors = fields.ManyToManyField('models.Author', related_name='books', description="作者")
+    class Meta:
+        table = "book"

+ 0 - 4
pyproject.toml

@@ -1,4 +0,0 @@
-[tool.aerich]
-tortoise_orm = "settings.TORTOISE_ORM"
-location = "./migrations"
-src_folder = "./."

+ 4 - 0
requirements.txt

@@ -0,0 +1,4 @@
+fastapi
+uvicorn[standard]
+tortoise-orm[aiomysql]
+jinja2

+ 4 - 4
settings.py

@@ -18,10 +18,10 @@ TORTOISE_ORM = {
     # 注册所有存放模型的文件/模块
     "apps": {
         "models": {
-            "models": ['models',"aerich.models"],
+            "models": ["models"],
             "default_connection": "default",
         }
     },
-    'use_tz':False,
-    'timezone':'Asia/Shanghai'
-}
+    "use_tz": False,
+    "timezone": "Asia/Shanghai",
+}

+ 7 - 7
templates/index.html

@@ -1,14 +1,14 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang="zh-CN">
 <head>
     <meta charset="UTF-8">
-    <title>书籍列表</title>
+    <title>电影列表</title>
 </head>
 <body>
-<p>{{ books.0 }}</p>
-<p>{{ books.1 }}</p>
-<p>{{ books.2 }}</p>
-<p>{{ books.3 }}</p>
+<p>{{ movies.0 }}</p>
+<p>{{ movies.1 }}</p>
+<p>{{ movies.2 }}</p>
+<p>{{ movies.3 }}</p>
 
 </body>
-</html>
+</html>