☰
綦江烟草系统学习
2026/9/25 21:28:41 网站建设 项目流程

一.一个系统由哪几块组成

前端画界面 + 收集用户操作
后端处理业务逻辑 + 调数据库 + 调算法
数据库存取数据
算法专门算风险分

API(接口)

定义:前端和后端之间的约定。前端说"我要什么",后端说"给你什么"。

这个项目里:后端定义了 40 多个 API,比如:

  • POST /api/auth/login—— 登录

  • GET /api/results/b—— 查 B 端结果

  • POST /api/tasks—— 创建算法任务

类比:菜单。服务员(前端)照着菜单(API)向后厨(后端)点菜。

一个 API 长什么样?

text

GET /api/results/b?page=1&pageSize=20
  • GET:我要"拿"数据(不是"给"数据)

  • /api/results/b:我要 B 端结果

  • ?page=1&pageSize=20:我要第 1 页,每页 20 条

二、第二课:一个请求的完整旅程

现在,我们真正打开代码,跟着一次点击走完全程。

场景:你在浏览器里点了一下左侧菜单的"B 端结果"。


【第 0 步】你在浏览器里看到了什么

左侧有一排菜单,其中一个是"B 端结果"。你点了一下。

这一步的代码在哪?

web/tobacco_system/frontend/src/layout/MainLayout.vue

这个文件就是左侧菜单的"图纸"。里面定义了 6 个菜单分组、15 个菜单项。"B 端结果"是其中之一,它对应一个路由地址,比如/b-results。

什么是路由?
你可以理解成"网址里的路径"。就像:

  • 你打开qijiang.com/b-results→ 显示 B 端结果页

  • 你打开qijiang.com/c-results→ 显示 C 端结果页

路由就是"哪个网址显示哪个页面"的对照表。


【第 1 步】前端:从"点击"到"发请求"

你点击后,前端做了两件事:

① 切换页面

src/router/index.ts这个文件里有一张路由表,它说:

text

/b-results → 显示 BResults.vue 这个页面组件

于是前端把页面换成B 端结果页。

② 页面加载时,自动去后端要数据

src/views/BResults.vue(B 端结果页)被打开后,它内部会执行一段代码,大意是:

javascript

// 打开页面时,去后端要第 1 页数据 const data = await getBResults({ page: 1, pageSize: 20 })

这里的getBResults就是一个API 调用函数。

所有 API 函数都在这个文件里:src/api/tobacco.ts

这个文件里有56 个函数,每个函数对应后端的一个接口。比如:

前端函数对应的后端接口
getBResultsGET /api/results/b
getCResultsGET /api/results/c
loginPOST /api/auth/login
createTaskPOST /api/tasks

【第 2 步】前端:真正发出"网络请求"

getBResults内部会调用一个更底层的工具:src/api/client.ts

这个文件是所有网络请求的"总闸"。它用的是浏览器自带的fetch函数(不是 axios),负责:

  • 拼出完整网址:http://你的服务器/api/results/b?page=1&pageSize=20

  • 带上身份令牌(Token):Authorization: Bearer xxxxx

  • 发送请求

  • 接收响应

  • 如果返回401(未登录),自动清空登录状态并跳回登录页

这一步发出去的东西,就是一个 HTTP 请求。

什么是 HTTP 请求?

你可以把它想象成一封信:

  • 信封上写地址:GET /api/results/b?page=1&pageSize=20(我要什么)

  • 信封里带证件:Authorization: Bearer xxxxx(我是谁)

  • 寄出去:通过互联网发给服务器

服务器收到后,回一封信(HTTP 响应),里面装着你要的数据。

请求长这样(简化版):

text

GET /api/results/b?page=1&pageSize=20 Authorization: Bearer eyJhbGci...

【第 3 步】后端:收到请求,找到对应的处理函数

请求飞到服务器后,被FastAPI(后端框架)接住。

FastAPI 做的事情:看请求的路径/api/results/b,去路由表里找,谁能处理这个路径。

所有的后端接口都定义在这一个文件里:app/api/routes.py

这个文件大约1000 行,包含40 多个接口。它就像后端的"总机"。

里面有一个函数专门处理/api/results/b,大概长这样(简化):

python

@router.get("/results/b", response_model=PaginatedBResults) def get_b_results( page: int = 1, pageSize: int = 20, current_user: CurrentUser = Depends(require_user), # ← 先验证登录 ): return query_b_results(page=page, page_size=pageSize)

逐行解释:

  • @router.get(...):装饰器,告诉 FastAPI "这个函数负责处理 GET /results/b"

  • response_model=PaginatedBResults:告诉 FastAPI "返回的数据要符合这个格式"

  • page: int = 1:从网址里读page参数,默认是 1

  • Depends(require_user):先检查你有没有登录,没登录直接返回 401

  • query_b_results(...):真正去查数据库的函数

什么是"装饰器"?
就是写在函数上面的@xxx那一行。它的作用是给这个函数贴个标签,告诉框架"这个函数是干嘛的"。
你不需要理解它的实现,只要知道:看到@router.get(...)就说明这是一个对外的 API。


【第 4 步】后端:去数据库捞数据

query_b_results这个函数在另一个文件里:

app/services/tobacco_db_service.py

这个文件是整个后端最大的一个(60KB),专门负责"从数据库查数据"。

它内部会做这些事:

python

# 1. 打开数据库连接 with get_anomaly_session() as session: # 2. 写一个查询:从 tobacco_b_result 表里,按风险分从高到低排序 stmt = select(TobaccoBResult).order_by(TobaccoBResult.final_risk_score.desc()) # 3. 加上"只查当前批次"的条件 stmt = stmt.where(TobaccoBResult.batch_id == current_batch_id) # 4. 分页:跳过前 N 条,取 20 条 stmt = stmt.offset((page - 1) * page_size).limit(page_size) # 5. 执行查询 rows = session.scalars(stmt).all() # 6. 顺便统计一下总共有多少条(给前端显示"共 XX 条") total = session.scalar(select(func.count()).select_from(TobaccoBResult)...)

关键点:这里出现了ORM(对象关系映射)。

什么是 ORM?

数据库里的表是二维表格(行和列)。Python 代码里操作表格很别扭。
ORM 的作用是:把每一行数据变成一个 Python 对象,你就可以像操作普通对象一样操作它。

比如数据库里有一行:

text

零售商ID: R001, 最终风险分: 92, 危险等级: L4

ORM 会把它变成一个对象:

python

row.零售商ID # "R001" row.final_risk_score # 92 row.risk_level # "L4"

这个项目用的 ORM 是 SQLAlchemy。

ORM 模型定义在哪?

app/models/tobacco.py

这个文件里定义了所有数据库表的"Python 版本"。比如:

python

class TobaccoBResult(AnomalyBase): __tablename__ = "tobacco_b_result" # 对应数据库里的表名 id = mapped_column(Integer, primary_key=True) batch_id = mapped_column(String(64)) retailer_id = mapped_column(String(64)) # 零售商ID final_risk_score = mapped_column(Float) # 最终风险分 risk_level = mapped_column(String(16)) # 危险等级 ...

记住这个对应关系:

数据库世界Python 世界
表tobacco_b_result类TobaccoBResult
一行数据一个对象(实例)
一列一个属性(row.final_risk_score)

【第 5 步】数据返回:从数据库 → 后端 → 前端

数据库把数据交给后端。后端做两件事:

① 包装成前端要的格式

后端不能直接把"数据库对象"扔给前端(前端不认识)。它要把数据转成 JSON。

什么是 JSON?

JSON 就是一种文本格式,长得像 Python 的字典和列表。所有语言都能读懂它。

例子:

json

{ "total": 1234, "items": [ { "retailerId": "R001", "finalRiskScore": 92, "riskLevel": "L4" }, { "retailerId": "R002", "finalRiskScore": 88, "riskLevel": "L4" } ] }

这个"转 JSON"的过程由 Pydantic 负责。Pydantic 的模型定义在:

app/schemas/tobacco.py

这个文件定义了所有"请求长什么样"和"响应长什么样",比如PaginatedBResults(分页的 B 端结果)。

Pydantic 的作用:约定数据的格式。它保证后端返回的 JSON,字段名和类型都符合前端期望。

② 返回响应

后端把 JSON 塞进 HTTP 响应,发回给前端。


【第 6 步】前端:拿到数据,渲染界面

前端收到 JSON 后,把它交给Vue处理。Vue 会:

  • 把items数组渲染成表格的每一行

  • 把total显示在"共 1234 条"

  • 把riskLevel渲染成彩色的标签(L4 红色、L3 橙色……)

"渲染"是什么?

就是"把数据变成你看到的画面"。Vue 的核心能力就是这个:数据一变,页面自动更新。


三、完整流程图(请截图保存)

text

你点击"B端结果" │ ▼ [前端] MainLayout.vue 菜单 → router/index.ts 路由 → BResults.vue 页面 │ ▼ [前端] BResults.vue 调 getBResults() ← src/api/tobacco.ts │ ▼ [前端] client.ts 用 fetch 发出 HTTP 请求 │ GET /api/results/b?page=1&pageSize=20 │ Authorization: Bearer xxx ▼ [网络] ────────────────────────────────────────► │ ▼ [后端] FastAPI 收到请求 → routes.py 找到 get_b_results 函数 │ ▼ [后端] Depends(require_user) 先验证登录 ✅ │ ▼ [后端] 调 query_b_results() ← tobacco_db_service.py │ ▼ [数据库] MySQL 执行 SQL,从 tobacco_b_result 表捞出 20 行 │ (表结构定义在 models/tobacco.py) ▼ [后端] SQLAlchemy 把每行变成对象 → Pydantic 转成 JSON │ (格式定义在 schemas/tobacco.py) ▼ [网络] ◄──────────────────────────────────────── │ HTTP 200 + JSON ▼ [前端] BResults.vue 拿到数据,Vue 渲染成表格 │ ▼ 你看到了 B 端结果表格 🎉

四、一个"陷阱"提醒(现在只需知道,不用深究)

这个项目有个很特别的设计:"当前批次"不存在请求里。

一般系统会在请求里带?batchId=xxx,但这个系统不是。它把"你当前选的批次"存在服务器的数据库里(tobacco_user_session表的selected_batch_id字段)。

后端在处理请求时,通过app/services/request_context.py这个工具,隐式地读到当前批次。

后果:同一个账号,在两个浏览器标签页里,不能选不同批次——后选的会覆盖先选的。

这个设计新手最容易踩坑,先记住现象就行,后面专门讲。


五、今天的小作业

这次作业全部是"找文件",不用写代码。请你在脑子里(或打开项目文件夹)回答:

  1. 前端:

    • 左侧菜单的定义在哪个文件?

    • 所有 API 调用函数集中在哪个文件?

    • 发网络请求的"总闸"是哪个文件?

  2. 后端:

    • 所有 API 接口定义在哪个文件?

    • 定义"响应数据格式"的文件是哪个?

    • 定义"数据库表"的文件是哪个?

  3. 流程题(用大白话):
    假设你在 B 端结果页,把每页改成 50 条。请按顺序说出,哪 6 个文件/环节会被涉及到?(提示:参考上面的流程图)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询