FastAPI+Sqlite+HTML的登錄注冊與文件上傳系統:完整實現指南

作為一名開發者,你是否曾想過親手搭建一個包含用戶注冊、登錄認證和文件上傳功能的完整 Web 系統?今天,我將帶大家一步步拆解一個基于FastAPI(后端)和原生 JavaScript(前端)的前后端分離項目,從核心功能實現到關鍵技術點解析,讓你快速掌握前后端協作的精髓。

最后附超詳細帶解析的源碼哦!

一、項目整體介紹:我們要做什么?

這個項目是一個極簡但完整的 Web 應用,核心功能包括:

  • 用戶注冊:支持用戶名和密碼注冊,包含前端表單驗證和后端數據校驗
  • 用戶登錄:基于 JWT(JSON Web Token)的身份認證,登錄后返回令牌
  • 權限控制:僅登錄用戶可訪問文件上傳功能
  • 文件上傳:支持二進制文件上傳,保存到服務器本地

整個系統采用前后端分離架構:

  • 前端:HTML+CSS + 原生 JavaScript,用 Axios 發送 HTTP 請求
  • 后端:FastAPI 框架,處理業務邏輯、數據庫交互和身份驗證
  • 數據庫:SQLite(輕量免配置,適合演示)
  • 通信方式:JSON 格式數據交互,文件上傳采用 multipart/form-data 格式

二、技術棧解析:為什么選這些工具?

在開始實現前,先了解下項目使用的核心技術棧及其優勢:

技術作用核心優勢
FastAPI后端框架高性能、自動生成 API 文檔、類型提示友好、支持異步
原生 JavaScript前端邏輯零依賴、兼容性好、適合理解 HTTP 請求本質
Axios前端 HTTP 庫支持 Promise、攔截器、請求 / 響應轉換,處理異步請求更優雅
SQLAlchemyORM 工具簡化數據庫操作,支持多種數據庫,避免手寫 SQL
JWT身份認證無狀態、適合分布式系統、減少數據庫查詢
bcrypt密碼加密單向哈希、抗暴力破解,比 MD5 等加密更安全

三、先看效果再看代碼?

1、注冊頁面:

要注意的是,我們前端設置了密碼校驗,要求賬號的密碼的最少長度都是6個長度,并且注冊成功自動跳轉登錄界面。

注冊后數據庫的密碼存儲使用哈希加密,避免了明文存儲,增加了用戶安全性。?

?

2、登錄界面

所有頁面都有錯誤提示框和成功的提示框,登錄成功自動跳轉主頁上傳文件。

?

3、文件上傳界面

如果沒有登錄成功,由于該項目加入了jwt校驗,直接訪問url會跳轉到登錄界面,極大的保護了API的安全性,阻止沒有權限的人上傳文件。

上傳失敗:

上傳成功:?

?成功文件的存放:

?

四、前端實現:用戶交互與請求處理

前端部分主要包含 3 個頁面:注冊頁(register.html)、登錄頁(login.html)和首頁(welcome.html)。我們重點解析核心邏輯:

1. 表單驗證:用戶輸入第一道防線

無論是注冊還是登錄,前端表單驗證都能減少無效請求,提升用戶體驗。以注冊頁為例:

// 注冊表單提交邏輯
document.querySelector('.register-form').onsubmit = function(e) {e.preventDefault(); // 阻止表單默認提交// 獲取用戶輸入const username = document.querySelector('#username').value.trim();const password = document.querySelector('#password').value.trim();const confirmPassword = document.querySelector('#password_isok').value.trim();// 前端校驗if (username.length < 6) {showError('用戶名至少6個字符');return;}if (password.length < 6) {showError('密碼至少6個字符');return;}if (password !== confirmPassword) {showError('兩次密碼不一致');return;}// 校驗通過,發送請求...
};

為什么要做前端校驗?

  • 即時反饋用戶輸入錯誤,無需等待后端響應
  • 減少無效的后端請求,降低服務器壓力
  • 提升用戶體驗,明確告知錯誤原因

2. Axios 請求:前后端數據橋梁

前端通過 Axios 與后端通信,核心是處理請求參數、請求頭和響應結果。以登錄請求為例:

// 登錄請求
axios({url: 'http://127.0.0.1:8080/api/login',method: 'post',data: {username: username,password: password}
}).then(response => {if (response.data.code === 200) {// 登錄成功,保存token到localStoragelocalStorage.setItem('token', response.data.data.access_token);// 跳轉到首頁setTimeout(() => window.location.href = 'welcome.html', 1000);}
}).catch(error => {// 處理錯誤(如用戶名密碼錯誤)showError(error.response.data.message);
});

這里的關鍵設計:

  • localStorage存儲 JWT 令牌,持久化保存(關閉瀏覽器不丟失)
  • 統一響應格式(code+message+data),便于前端統一處理
  • setTimeout實現登錄成功后的延遲跳轉,給用戶提示時間

3. 權限控制:保護敏感頁面

首頁(文件上傳頁)需要驗證用戶是否登錄,否則強制跳轉登錄頁:

// 頁面加載時驗證登錄狀態
window.addEventListener("DOMContentLoaded", function() {const token = localStorage.getItem('token');if (!token || token.trim() === "") {// 未登錄,提示并跳轉showError("您尚未登錄,正在跳轉至登錄頁...");setTimeout(() => window.location.href = 'login.html', 1500);}
});

權限控制的核心思路:

  • 前端:通過檢查localStorage中的 token 判斷登錄狀態(簡單驗證)
  • 后端:每次請求驗證 token 有效性(安全驗證,防止前端篡改)

4. 文件上傳:二進制數據處理

文件上傳是前端的一個特殊場景,需要用FormData構造請求體:

// 文件上傳處理
const formData = new FormData();
formData.append("file", file); // 添加文件對象// 發送帶token的上傳請求
axios.post('http://127.0.0.1:8080/api/upload_binary', formData, {headers: {'Content-Type': 'multipart/form-data', // 文件上傳專用格式'Authorization': `Bearer ${localStorage.getItem('token')}` // 攜帶token}
}).then(response => {showSuccess(`文件 ${file.name} 上傳成功`);
});

文件上傳的關鍵點:

  • Content-Type必須設為multipart/form-data,告訴服務器這是文件上傳請求
  • 通過Authorization頭攜帶 JWT 令牌,后端驗證用戶權限
  • FormData對象包裝文件數據,無需手動處理二進制格式

五、后端實現:業務邏輯與安全校驗

后端基于 FastAPI 實現,核心功能包括用戶管理、JWT 認證和文件上傳。我們逐一解析:

1. 項目初始化:配置與依賴

首先需要初始化 FastAPI 應用,配置數據庫和跨域支持:

# 導入核心庫
from fastapi import FastAPI, HTTPException, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base
import jwt
from passlib.context import CryptContext# 初始化FastAPI應用
app = FastAPI()# 配置CORS(跨域資源共享)
app.add_middleware(CORSMiddleware,allow_origins=["*"],  # 允許所有源(生產環境需指定具體域名)allow_methods=["*"],  # 允許所有HTTP方法allow_headers=["*"]   # 允許所有請求頭
)# 配置數據庫(SQLite)
DATABASE_URL = "sqlite:///users.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
Session = sessionmaker(bind=engine)
Base = declarative_base()

為什么需要 CORS?
前后端分離時,前端頁面和后端 API 通常不在同一域名下,瀏覽器會限制跨域請求。通過配置 CORS,后端明確允許前端域名的請求,解決 "跨域錯誤"。

2. 數據模型:數據庫與請求響應格式

用 SQLAlchemy 定義用戶表結構,用 Pydantic 定義請求 / 響應格式:

# 數據庫模型(用戶表)
class User(Base):__tablename__ = "users"id = Column(Integer, primary_key=True, index=True)username = Column(String(255), unique=True, index=True, nullable=False)password = Column(String(255), nullable=False)  # 存儲哈希后的密碼# 響應模型(統一格式)
class ResponseModel(BaseModel):code: int  # 狀態碼:200成功,400客戶端錯誤,500服務器錯誤message: str  # 提示信息data: Optional[dict] = None  # 可選數據

?統一響應格式的好處:
前端可以用同一套邏輯解析所有接口響應,無需為每個接口單獨處理格式,例如:

// 前端統一處理響應
if (response.data.code === 200) {// 成功邏輯
} else {// 錯誤提示showError(response.data.message);
}

3. 用戶注冊:數據校驗與密碼安全

注冊接口需要實現兩個核心功能:用戶名唯一性校驗和密碼加密存儲:

@app.post("/api/register", response_model=ResponseModel)
async def register(user: UserRegister):db = Session()try:# 檢查用戶名是否已存在existing_user = db.query(User).filter(User.username == user.username).first()if existing_user:return ResponseModel(code=400, message="用戶名已存在")# 密碼加密(關鍵!絕不能明文存儲)hashed_password = pwd_context.hash(user.password)new_user = User(username=user.username, password=hashed_password)# 保存到數據庫db.add(new_user)db.commit()return ResponseModel(code=200, message="注冊成功")finally:db.close()

密碼安全的關鍵:

  • 使用passlib庫的bcrypt算法哈希密碼(單向加密,無法解密)
  • 哈希過程會自動添加隨機鹽值,相同密碼哈希結果不同,防止彩虹表攻擊

4. JWT 認證:無狀態登錄驗證

JWT(JSON Web Token)是實現無狀態認證的核心,登錄成功后生成 token,后續請求攜帶 token 即可驗證身份:

# 生成JWT令牌
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):to_encode = data.copy()# 設置過期時間(默認30分鐘)expire = datetime.utcnow() + (expires_delta or timedelta(minutes=30))to_encode.update({"exp": expire})  # 添加過期時間字段# 生成token(密鑰+算法)return jwt.encode(to_encode, SECURITY_KET, algorithm=ALGORITHMS)# 登錄接口
@app.post("/api/login", response_model=ResponseModel)
async def login(user: UserLogin):db = Session()try:# 查找用戶db_user = db.query(User).filter(User.username == user.username).first()if not db_user:return ResponseModel(code=400, message="用戶名或密碼錯誤")# 驗證密碼(哈希比對)if not pwd_context.verify(user.password, db_user.password):return ResponseModel(code=400, message="用戶名或密碼錯誤")# 生成tokenaccess_token = create_access_token(data={"sub": user.username})return ResponseModel(code=200,message="登錄成功",data={"access_token": access_token, "token_type": "bearer"})finally:db.close()

JWT 的優勢:

  • 無狀態:服務器不需要存儲用戶登錄狀態,減輕服務器壓力
  • 跨域支持:適合分布式系統,多個服務可共用同一套認證機制
  • 攜帶信息:token 中可包含用戶基本信息(如用戶名),減少數據庫查詢

5. 文件上傳:權限驗證與文件存儲

文件上傳接口需要先驗證用戶 token,再處理文件存儲:

@app.post("/api/upload_binary", response_model=ResponseModel)
async def upload_binary_file(file: UploadFile = File(...),  # 接收文件token: str = Header(None, alias="Authorization")  # 接收token
):try:# 1. 驗證token(簡化版,實際項目建議用依賴注入)if not token or not token.startswith("Bearer "):return ResponseModel(code=401, message="未授權,請先登錄")token = token.split(" ")[1]try:# 解析token,驗證有效性payload = jwt.decode(token, SECURITY_KET, algorithms=[ALGORITHMS])except:return ResponseModel(code=401, message="token無效或已過期")# 2. 保存文件upload_dir = "uploads_binary"if not os.path.exists(upload_dir):os.makedirs(upload_dir)  # 創建目錄file_path = os.path.join(upload_dir, file.filename)with open(file_path, "wb") as buffer:buffer.write(await file.read())  # 寫入文件return ResponseModel(code=200, message=f"文件 {file.filename} 上傳成功")except Exception as e:return ResponseModel(code=500, message="文件上傳失敗")

文件上傳的注意事項:

  • 目錄權限:確保服務器對uploads_binary目錄有寫入權限
  • 文件大小限制:實際項目中需限制文件大小,防止惡意上傳大文件
  • 文件名處理:可能需要重命名文件(如添加時間戳),避免同名文件覆蓋

六、項目源碼和運行

有了這些直接無腦運行,再無后顧之憂。

1、項目結構

注意:uploads_binary不需己創建,數據庫不需要自己創建,系統運行自己創建。

2、項目源碼

①login.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>用戶登錄</title><style>body {font-family: Arial, sans-serif;max-width: 400px;margin: 0 auto;padding: 20px;}.form-container {margin-bottom: 20px;padding: 20px;border: 1px solid #ddd;border-radius: 5px;}h1 {text-align: center;margin-top: 0;}input {display: block;width: 100%;padding: 8px;margin-bottom: 10px;border: 1px solid #ddd;border-radius: 4px;box-sizing: border-box;}button {background-color: #28cccf;color: white;padding: 10px 15px;border: none;border-radius: 4px;cursor: pointer;width: 100%;}button:hover {background-color: #1afaff;}.alert {font-size: 20px;text-align: center;margin-top: 20px;border: 1px solid #ddd;border-radius: 4px;display: none;padding: 10px;}.alert.success {background-color: #d4edda;color: #155724;border-color: #c3e6cb;}.alert.error {background-color: #f8d7da;color: #721c24;border-color: #f5c6cb;}.register-link {text-align: center;margin-top: 15px;}.register-link a {color: #28cccf;text-decoration: none;}.register-link a:hover {text-decoration: underline;}</style>
</head>
<body><div class="form-container"><h1>用戶登錄</h1><!-- 登錄表單 --><form id="loginForm"><input type="text" name="username" placeholder="用戶名" required /><input type="password" name="password" placeholder="密碼" required /><button type="submit">登錄</button></form><!-- 提示信息 --><div class="alert success" id="successAlert" style="display: none;"></div><div class="alert error" id="errorAlert" style="display: none;"></div><!-- 注冊鏈接 --><div class="register-link">沒有賬號?<a href="register.html">去注冊</a></div></div><!-- 引入 axios --><script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script>
<script>document.getElementById('loginForm').addEventListener('submit', function (e) {e.preventDefault();const form = e.target;const username = form.username.value.trim();const password = form.password.value.trim();const successDiv = document.getElementById('successAlert');const errorDiv = document.getElementById('errorAlert');// 清空上次提示successDiv.style.display = 'none';errorDiv.style.display = 'none';successDiv.textContent = '';errorDiv.textContent = '';// 發送登錄請求axios({url:'http://127.0.0.1:8080/api/login',method: "post",data:{username: username,password: password}}).then(response => {if (response.data.code === 200) {successDiv.style.display = 'block';successDiv.textContent = response.data.message;// 如果返回了 token,將其保存到 localStorage 中if (response.data.data && response.data.data.access_token) {// 將登錄成功后服務器返回的 token 保存到瀏覽器的本地存儲中,以便后續請求時使用localStorage.setItem('token', response.data.data.access_token);  //localStorage.setItem(key, value) 是瀏覽器提供的一個用于持久化存儲數據的方法}// 跳轉頁面setTimeout(() => {window.location.href = 'welcome.html';}, 1000);} else {errorDiv.style.display = 'block';errorDiv.textContent = response.data.message;}}).catch(error => {errorDiv.style.display = 'block';errorDiv.textContent = "登錄失敗:" +(error.response?.data?.message || error.message);});});
</script></body>
</html>

②register.html?

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>用戶注冊</title><style>body {font-family: Arial, sans-serif;max-width: 400px;margin: 0 auto;padding: 20px;}.form-container {margin-bottom: 20px;padding: 20px;border: 1px solid #ddd;border-radius: 5px;}h1 {text-align: center;margin-top: 0;}input {display: block;width: 100%;padding: 8px;margin-bottom: 10px;border: 1px solid #ddd;border-radius: 4px;box-sizing: border-box;}button {background-color: #28cccf;color: white;padding: 10px 15px;border: none;border-radius: 4px;cursor: pointer;width: 100%;}button:hover {background-color: #1afaff;}.login-link {text-align: center;margin-top: 15px;}.login-link a {color: #1afaff;text-decoration: none;}.login-link a:hover {text-decoration: underline;}.alert {font-size: 20px;text-align: center;margin-top: 20px;border: 1px solid #ddd;border-radius: 4px;display: none;padding: 10px;}.alert.success {background-color: #d4edda;color: #155724;border-color: #c3e6cb;}.alert.error {background-color: #f8d7da;color: #721c24;border-color: #f5c6cb;}</style>
</head>
<body><div class="form-container"><h1>用戶注冊</h1><form class="register-form"><input type="text" name="username" placeholder="用戶名" id="username" required><input type="password" name="password" placeholder="密碼" id="password" required><input type="password" name="password" placeholder="確認密碼" id="password_isok" required><button type="submit" class="btn-register" id="subtn">注冊</button></form><div class="login-link">已有賬號?<a href="login.html">去登錄</a></div><!-- 提示框容器 --><div class="alert success" id="successAlert" style="display: none;"></div><div class="alert error" id="errorAlert" style="display: none;"></div></div><script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script><script>document.querySelector('.register-form').onsubmit = function (e) {e.preventDefault();const username = document.querySelector('#username').value.trim();const password = document.querySelector('#password').value.trim();const confirmPassword = document.querySelector('#password_isok').value.trim();const successDiv = document.getElementById('successAlert');const errorDiv = document.getElementById('errorAlert');// 清空上次提示并隱藏successDiv.style.display = 'none';errorDiv.style.display = 'none';successDiv.textContent = '';errorDiv.textContent = '';// 前端校驗if (username.length < 6) {errorDiv.style.display = 'block';errorDiv.textContent = '用戶名至少6個字符';return;}if (password.length < 6) {errorDiv.style.display = 'block';errorDiv.textContent = '密碼至少6個字符';return;}if (password !== confirmPassword) {errorDiv.style.display = 'block';errorDiv.textContent = '兩次密碼不一致';return;}// 發送請求axios({url: 'http://127.0.0.1:8080/api/register',method: 'post',data: {username: username,password: password}}).then(result => {if (result.data.code === 200) {successDiv.style.display = 'block';successDiv.textContent = result.data.message;setTimeout(function () {window.location.href = 'login.html';}, 1000)// 注冊成功后清空表單document.querySelector('#username').value = "";document.querySelector('#password').value = "";document.querySelector('#password_isok').value = "";} else {errorDiv.style.display = 'block';errorDiv.textContent = result.data.message;}}).catch(error => {errorDiv.style.display = 'block';errorDiv.textContent = "注冊失敗:" +(error.response?.data?.detail || error.response?.data?.message || error.message);});};</script>
</body>
</html>

③welcome.html?

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>首頁</title><style>body {font-family: Arial, sans-serif;max-width: 400px;margin: 0 auto;padding: 20px;}.form-container {margin-bottom: 20px;padding: 20px;border: 1px solid #ddd;border-radius: 5px;}p {text-align: center;font-size: 20px;font-weight: bold;}h1, h2 {text-align: center;margin-top: 0;}input[type="file"] {display: block;width: 100%;padding: 8px;margin-bottom: 10px;border: 1px solid #ddd;border-radius: 4px;box-sizing: border-box;}button {background-color: #28cccf;color: white;padding: 10px 15px;border: none;border-radius: 4px;cursor: pointer;width: 100%;}button:hover {background-color: #1afaff;}.alert {font-size: 20px;text-align: center;margin-top: 20px;border: 1px solid #ddd;border-radius: 4px;display: none;padding: 10px;}.alert.success {background-color: #d4edda;color: #155724;border-color: #c3e6cb;}.alert.error {background-color: #f8d7da;color: #721c24;border-color: #f5c6cb;}</style>
</head>
<body><h1>歡迎回來!</h1><p>您已成功登錄。</p><!-- 文件上傳表單 --><form id="uploadForm" class="form-container" style="margin-top: 40px;"><h2>上傳文件</h2><input type="file" id="fileInput" name="file" required /><button type="submit">上傳</button></form><!-- 提示信息 --><div class="alert success" id="successAlert" style="display: none;"></div><div class="alert error" id="errorAlert" style="display: none;"></div><!-- 引入 axios --><script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script><script>// 頁面加載時檢查是否有 token,沒有則跳轉到登錄頁面并提示window.addEventListener("DOMContentLoaded", function () {const token = localStorage.getItem('token');const warningDiv = document.getElementById('errorAlert');if (!token || token.trim() === "") {warningDiv.style.display = 'block';warningDiv.innerText = "您尚未登錄,正在跳轉至登錄頁...";setTimeout(() => {window.location.href = 'login.html';}, 1500);} else {// token 存在,繼續加載頁面內容warningDiv.style.display = 'none';}});// 文件上傳處理document.getElementById('uploadForm').addEventListener('submit', async function (e) {e.preventDefault();const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];const successDiv = document.getElementById('successAlert');const errorDiv = document.getElementById('errorAlert');// 清空上次提示successDiv.style.display = 'none';errorDiv.style.display = 'none';successDiv.textContent = '';errorDiv.textContent = '';if (!file) {errorDiv.style.display = 'block';errorDiv.textContent = '請選擇一個文件';return;}// 創建一個空的 FormData 對象,用于構建 HTTP 請求中需要發送的數據體。const formData = new FormData();// 將用戶選擇的文件(變量 file)附加到 FormData 對象中,字段名為 "file"。這與后端接收文件的鍵名保持一致。formData.append("file", file);await axios.post('http://127.0.0.1:8080/api/upload_binary', formData, {headers: {// 顯式聲明請求內容類型為 multipart/form-data,這是上傳文件的標準格式。'Content-Type': 'multipart/form-data',  // 支持將文本、二進制文件和其他類型的數據// 這行代碼用于從瀏覽器的 localStorage 中獲取名為 'token' 的 用戶身份憑證(Token),// 并將其作為 Bearer Token 添加到 HTTP 請求頭中,以完成對后端接口的身份認證。'Authorization': `Bearer ${localStorage.getItem('token')}`}}).then(response =>{if (response.data.code === 200) {successDiv.style.display = 'block';successDiv.textContent = response.data.message;fileInput.value = ''; // 清空文件選擇框} else {errorDiv.style.display = 'block';errorDiv.textContent = response.data.message;}}).catch (error=>{errorDiv.style.display = 'block';errorDiv.textContent = "上傳失敗:" +(error.response?.data?.message || error.message);});});</script>
</body>
</html>

?④Register_API.py

# 導入 FastAPI 框架核心模塊,用于創建 Web API 應用
from fastapi import FastAPI, HTTPException
# 用于處理跨域請求(CORS),允許前端訪問后端接口(解決跨域問題)
from fastapi.middleware.cors import CORSMiddleware
from jose.constants import ALGORITHMS
# pydantic 的 BaseModel 用于定義請求體的數據模型(數據校驗)
# Field 用于為模型字段添加額外信息或約束
# constr 是一個字符串類型約束工具,例如可以限制字符串長度、正則匹配等
from pydantic import BaseModel, Field, constr
import sqlite3
# Optional 用于標注某個字段可以為 None,常用于定義可選字段的數據模型
from typing import Optional
# 用于創建數據庫引擎,常用于同步數據庫連接
from sqlalchemy import create_engine, Column, Integer, String
# 用于創建數據庫會話,用于執行數據庫操作
from sqlalchemy.orm import sessionmaker, declarative_base
# 用于處理文件讀寫
import os
from datetime import datetime, timedelta
from typing import Optional
import jwt  # 用于生成和解析 JWT token
from passlib.context import CryptContext   # 哈希加密
# UploadFile:表示一個上傳的文件對象,包含文件名、類型、內容等信息
# File:是一個類,用于作為參數的默認值,配合 UploadFile 使用,表示該參數必須是一個上傳的文件
from fastapi import UploadFile, FileSECURITY_KET = "asdfghjklzxcvbnm"  # 密鑰
ALGORITHMS = "HS256"  #加密的算法
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # token有效期為30分鐘pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")# 創建訪問令牌
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):"""創建訪問令牌:param data: 要編碼的數據(通常是用戶信息):param expires_delta: 過期時間"""to_encode = data.copy()# 設置過期時間# 設置過期時間if expires_delta:expire = datetime.utcnow() + expires_deltaelse:expire = datetime.utcnow() + timedelta(minutes=15)# 添加過期時間字段to_encode.update({"exp": expire})# 使用 jwt 庫生成 token    (  加密內容,    加密秘鑰,         加密算法     )encoded_jwt = jwt.encode(to_encode, SECURITY_KET, algorithm=ALGORITHMS)return encoded_jwt# 創建 FastAPI 實例對象,這是整個應用的核心
app = FastAPI()# 添加CORS中間件,允許跨域傳輸
app.add_middleware(CORSMiddleware,allow_origins=["*"],  # 允許所有源allow_credentials=True,  # 是否允許發送 Cookieallow_methods=["*"],  # 允許所有HTTP方法allow_headers=["*"],  # 允許所有HTTP頭部
) # 定義數據庫連接URL
DATABASE_URL = "sqlite:///users.db"# 創建基類
Base = declarative_base()# 創建數據庫引擎,設置連接參數以允許在多線程環境中使用(地址)
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})# 創建會話,綁定數據庫引擎
Session = sessionmaker(bind=engine, autocommit=False, autoflush=False, expire_on_commit=False)# 創建數據庫表結構(可以創建數據庫表結構)
class User(Base):__tablename__ = "users"id = Column(Integer, primary_key=True, index=True)username = Column(String(255), unique=True, index=True, nullable=False)password = Column(String(255), nullable=False)class Token(BaseModel):"""用于響應 token 的數據模型"""access_token: strtoken_type: str# 執行創建數據庫表結構
Base.metadata.create_all(bind=engine)# 定義注冊接口的請求數據模型
class UserRegister(BaseModel):# 用戶名字段:# - 至少 3 個字符長# - 只能包含英文字母、數字和中文字符username: str = Field(min_length=6, pattern='^[a-zA-Z0-9\u4e00-\u9fa5]+$')# 密碼字段:# - 至少 6 個字符長password: constr(min_length=6)# 定義統一的響應數據模型,便于前端解析處理結果
class ResponseModel(BaseModel):code: int  # 狀態碼(200 表示成功,400 表示客戶端錯誤,500 表示服務器錯誤)message: str  # 描述信息(如“注冊成功”、“用戶名已存在”)data: Optional[dict] = None  # 可選返回數據,默認為 None# 定義登錄請求的數據模型
class UserLogin(BaseModel):username: strpassword: str# 定義文件上傳請求數據模型
class UploadRequest(BaseModel):filename: strcontent: str# 登錄接口
@app.post("/api/login", response_model=ResponseModel)
async def login(user: UserLogin):db = Session()try:db_user = db.query(User).filter(User.username == user.username).first()if not db_user:return ResponseModel(code=400, message="用戶名或密碼錯誤")# 驗證密碼是否匹配if not pwd_context.verify(user.password, db_user.password):return ResponseModel(code=400, message="用戶名或密碼錯誤")access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)access_token = create_access_token(data={"sub": user.username},expires_delta=access_token_expires)return ResponseModel(code=200,message="登錄成功",data={"access_token": access_token, "token_type": "bearer"})except Exception as e:print("服務器錯誤詳情:", str(e))return ResponseModel(code=500, message="服務器錯誤")finally:db.close()# 定義上傳文件的接口
@app.post("/api/upload_binary", response_model=ResponseModel)
async def upload_binary_file(#File(...) 表示該參數是一個文件類型的參數,并且是必填項(... 是 Python 的 Ellipsis,表示必填)# UploadFile 是 FastAPI 提供的一個類,用于表示上傳的文件。file: UploadFile = File(...),# Optional[str] 表示這個參數可以不傳,默認為 Nonefilename: Optional[str] = None
):try:# 創建存儲文件的目錄upload_dir = "uploads_binary"if not os.path.exists(upload_dir):os.makedirs(upload_dir)# 使用自定義文件名或原始文件名save_filename = filename if filename else file.filenamefile_path = os.path.join(upload_dir, save_filename)# 寫入文件(異步方式)with open(file_path, "wb") as buffer:buffer.write(await file.read())return ResponseModel(code=200, message=f"文件 {save_filename} 上傳成功")except Exception as e:print("文件上傳失敗:", str(e))return ResponseModel(code=500, message="文件上傳失敗")# 注冊接口
@app.post("/api/register", response_model=ResponseModel)  # response_model=ResponseModel:表示這個接口返回的數據結構必須符合 ResponseModel 的格式
async def register(user: UserRegister):  # user: UserRegister表示這個函數接收一個參數 user,它的數據結構由 UserRegister 定義try:db = Session()# 查詢用戶名是否已存在existing_user = db.query(User).filter(User.username == user.username).first()if existing_user:# 如果用戶名已存在,拋出 HTTP 異常,提示“用戶名已存在”,前端執行 catch 塊,顯示錯誤信息raise HTTPException(status_code=400, detail="用戶名已存在")# 使用哈希加密存儲密碼hase_password = pwd_context.hash(user.password)new_user = User(username=user.username, password=hase_password)# 將新用戶插入到數據庫中db.add(new_user)db.commit()db.refresh(new_user)return ResponseModel(code=200, message="注冊成功")except HTTPException as e:# 如果用戶名已存在,拋出 HTTP 異常,前端執行 catch 塊,顯示錯誤信息return ResponseModel(code=e.status_code, message=e.detail)except Exception as e:# 如果發生異常,回滾事務,并返回錯誤信息print("服務器錯誤詳情:", str(e))db.rollback()return ResponseModel(code=500, message="服務器錯誤")finally:db.close()if __name__ == "__main__":import uvicornuvicorn.run(app, host="127.0.0.1", port=8080)

3、項目運行

①安裝后端依賴:

pip install fastapi uvicorn sqlalchemy python-jose passlib[bcrypt]

?②先運行后端再運行前端

運行后端:

運行前端:

七、總結:前后端分離開發的核心思路

通過這個項目,我們可以總結出前后端分離開發的關鍵原則:

  • 職責清晰:前端負責用戶交互和數據展示,后端負責業務邏輯和數據存儲
  • 接口先行:前后端約定好接口文檔(FastAPI 自動生成),并行開發
  • 數據安全:敏感數據(如密碼)必須在后端處理,前端只做展示和基礎驗證
  • 狀態管理:前端負責維護客戶端狀態(如登錄狀態),后端通過 token 驗證身份

如果你是前端開發者,這個項目能幫你理解后端的認證邏輯;如果你是后端開發者,能讓你更清晰前端的請求處理方式。關注我,后續會帶來更多前后端實戰項目解析!

你在開發中遇到過哪些前后端協作的坑?歡迎在評論區分享你的解決方案,有不懂的都可以來問小寧哦~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/912900.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/912900.shtml
英文地址,請注明出處:http://en.pswp.cn/news/912900.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【動態規劃】P11188 「KDOI-10」商店砍價|普及+

本文涉及知識點 C動態規劃 P11188 「KDOI-10」商店砍價 題目背景 English Statement. You must submit your code at the Chinese version of the statement. 您可以點擊 這里 下載本場比賽的選手文件。 You can click here to download all tasks and examples of the c…

國產LHR3040芯片是REF5040的代替品

LHR3040是一款噪聲低、漂移低、精度高的電壓基準產品系列。這些基準同時支持灌電流和拉電流&#xff0c;并且具有出色的線性和負載調節性能。采用專有的設計技術實現了出色的溫漂(3ppm/℃)和高精度(0.05%)。這些特性與極低噪聲相結合&#xff0c;使LHR30XX系列成為高精度數據采…

專題:2025AI營銷市場發展研究報告|附400+份報告PDF匯總下載

原文鏈接&#xff1a;https://tecdat.cn/?p42800 在數字化浪潮席卷全球的當下&#xff0c;AI營銷正成為驅動企業增長的核心動力。 從市場規模來看&#xff0c;AI營銷正經歷著爆發式增長&#xff0c;生成式AI的出現更是為其注入了強大活力。在應用層面&#xff0c;AI已滲透到營…

深入對比 Python 中的 `__repr__` 與 `__str__`:選擇正確的對象表示方法

文章目錄 核心概念對比1. 根本目的差異2. 調用場景對比深入解析:何時使用哪種方法場景 1:開發者調試 vs 用戶展示場景 2:技術表示 vs 簡化視圖高級對比:特殊場景處理1. 容器中的對象表示2. 日志記錄的最佳實踐3. 異常信息展示最佳實踐指南1. 何時實現哪個方法?2. 實現原則…

萬能公式基分析重構補丁復分析和歐拉公式原理推導

基分析&#xff0c; x11 x2-1 x3i 存在加法法則 x1x20 所以x1-x2 存在鏈式基乘法法則 x1x1*x1x2*x2 x2x3*x3 x3x1*x3 -x1x2x3 將鏈式基乘法操作 二次&#xff0c;三次&#xff0c;直至n次化簡得 一次 x1 -x1 x3 矩陣 x1 x1 x2 x2 x3 …

OpenCV 4.10.0 移植

OpenCV 4.10.0 移植使用 概述移植編譯下載解壓編譯環境編譯 編譯完成OpenCV 庫文件及其作用 使用實例參考代碼 參考 概述 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是計算機視覺領域最廣泛使用的開源庫之一&#xff0c;提供了豐富的功能模塊&#xf…

Tomcat10.0以上版本編譯成功但報錯HTTP狀態 404

Tomcat正常啟動且項目已成功部署&#xff0c;但出現404錯誤。 HTTP狀態 404 - 未找到package org.example;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpSer…

在Flask項目中用Git LFS管理大文件(PDF)的完整實踐

在Flask項目中用Git LFS高效管理大文件(以農機說明書PDF為例) 背景與需求 在農機管理系統等實際項目中,經常需要上傳和管理大量超大文件(如200MB以上的PDF說明書、圖片等)。如果直接用Git管理這些大文件,不僅會導致倉庫膨脹、clone/pull速度變慢,還可能遇到推送失敗等…

樸素貝葉斯算法案例演示及Python實現

目錄 一、基本原理二、案例演示2.1 未平滑處理2.2 Laplace平滑處理 三、Python實現 一、基本原理 樸素貝葉斯思想&#xff1a;依靠特征概率去預測分類&#xff0c;針對于代分類的樣本&#xff0c;會求解在該樣本出現的條件下&#xff0c;各個類別出現的概率&#xff0c;哪個類…

RAG從入門到高階(二):Retrieve-and-Rerank

在上一篇教程中&#xff0c;我們了解了 Naive RAG 的基本原理和實現。它就像一個剛剛學會查找資料的新手&#xff0c;雖然能找到一些信息&#xff0c;但有時候找到的并不夠精準&#xff0c;甚至會有一些無關的干擾。 今天&#xff0c;我們將介紹 Retrieve-and-Rerank RAG&…

【腳本】Linux磁盤目錄掛載腳本(不分區)

以下是一個不帶分區&#xff0c;直接掛載整個磁盤到指定目錄的腳本。該腳本會檢查磁盤是否已掛載&#xff0c;自動創建文件系統&#xff08;可選&#xff09;&#xff0c;并配置開機自動掛載&#xff1a; #!/bin/bash# 磁盤直接掛載腳本&#xff08;不分區&#xff09; # 使用…

壁紙網站分享

壁紙網站鏈接&#xff1a; 1.Microsoft Design - Wallpapers&#xff1a;https://wallpapers.microsoft.design/?refwww.8kmm.com 2.哲風壁紙&#xff1a;https://haowallpaper.com/wallpaperForum 3.壁紙湖&#xff1a;https://bizihu.com/ 4.極簡壁紙&#xff1a;https://bz…

XILINX FPGA如何做時序分析和時序優化?

時序分析和時序優化是FPGA開發流程中關鍵步驟&#xff0c;確保設計在目標時鐘頻率下正確運行&#xff0c;避免時序違例&#xff08;如建立時間或保持時間不足&#xff09;。以下以Xilinx Kintex-7系列FPGA為例&#xff0c;詳細介紹時序分析和時序優化的方法、工具、流程及實用技…

linux screen輕松管理長時間運行的任務

以下是針對 Alpine Linux 環境下 screen 的安裝與使用指南&#xff0c;結合遷移數據場景的具體操作步驟&#xff1a; 1. 安裝 screen? 在 Alpine Linux 中需通過 apk 安裝&#xff08;非默認預裝&#xff09;&#xff1a; apk add screen 驗證安裝&#xff1a; screen --…

VR制作公司業務范圍

VR制作公司概念、能力與服務范圍 虛擬現實&#xff08;Virtual Reality, VR&#xff09;技術&#xff0c;作為當代科技的前沿領域&#xff0c;通過計算機技術模擬出真實或虛構的世界環境&#xff0c;使用戶能夠沉浸其中并進行交互體驗。VR制作公司&#xff0c;是這一領域的專業…

STM32之28BYJ-48步進電機驅動

目錄 一、引言 二、28BYJ-48步進電機簡介 2.1 基本特性 2.2 內部結構 2.3 工作模式 2.4 驅動原理 2.5 性能特點 2.6 驅動方案 2.7 使用注意事項 三、ULN2003驅動板簡介 3.1 基本概述 3.2 電路結構 3.3 驅動原理 3.4 接口定義 3.5 使用注意事項 四、…

TDSQL如何查出某一列中的逗號數量

在 TDSQL 中&#xff0c;要統計某一列里逗號的數量&#xff0c;可借助字符串函數來實現。下面為你介紹具體的實現方法&#xff1a; sql SELECT your_column,LENGTH(your_column) - LENGTH(REPLACE(your_column, ,, )) AS comma_count FROM your_table;下面對這段 SQL 進行詳細…

如何避免服務器出現故障情況?

服務器作為存儲數據信息的重要網絡設備&#xff0c;能夠保護企業重要數據的安全性&#xff0c;但是隨著網絡攻擊的不斷拓展&#xff0c;各個行業中的服務器也會遭受到不同類型的網絡攻擊&#xff0c;嚴重的會導致服務器業務中斷出現故障&#xff0c;給企業帶來巨大的經濟損失。…

C++ 優先級隊列

一、引言 隊列的特性是先進先出。優先級隊列的本質是一個有序隊列&#xff0c;根據成員的優先級&#xff0c;對隊列中的成員進行排序。優先級隊列默認是大頂堆&#xff0c;即堆頂元素最大 二、常用函數 empty()size()top()push()emplace()pop()swap() 三、代碼示例 class …