Skip to content

Latest commit

 

History

History
600 lines (486 loc) · 14.6 KB

File metadata and controls

600 lines (486 loc) · 14.6 KB

1.3 前后端交互

📖 概述

前后端交互是现代 Web 开发的核心概念,它描述了前端(客户端)和后端(服务器端)之间的数据交换和通信方式。本章将介绍前后端分离架构、数据交换格式、AJAX 技术等关键概念。

🏗️ 前后端分离架构

传统架构 vs 前后端分离

传统架构(服务端渲染)

浏览器 → 服务器 → 数据库
   ↑         ↓
   ← HTML 页面 ←

特点:

  • 服务器生成完整的 HTML 页面
  • 页面刷新时重新加载整个页面
  • 前后端耦合度高
  • 开发效率相对较低

前后端分离架构

前端应用 ←→ API 服务器 ←→ 数据库
   ↓           ↓
HTML/CSS/JS   JSON/XML

特点:

  • 前端和后端独立开发
  • 通过 API 进行数据交换
  • 支持单页应用(SPA)
  • 更好的用户体验

架构优势

  1. 开发效率:前后端可以并行开发
  2. 技术栈灵活:前端和后端可以使用不同的技术栈
  3. 可维护性:代码结构清晰,职责分离
  4. 可扩展性:支持多端应用(Web、移动端、桌面端)
  5. 用户体验:支持无刷新交互

📤 JSON 数据交换

JSON 简介

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,也易于机器解析和生成。

JSON 语法

{
  "string": "字符串",
  "number": 123,
  "boolean": true,
  "null": null,
  "array": [1, 2, 3],
  "object": {
    "name": "张三",
    "age": 25
  }
}

Python 中的 JSON 处理

import json

# Python 对象转 JSON 字符串
data = {
    "name": "张三",
    "age": 25,
    "skills": ["Python", "JavaScript", "Vue.js"]
}

json_string = json.dumps(data, ensure_ascii=False, indent=2)
print(json_string)

# JSON 字符串转 Python 对象
json_data = '{"name": "李四", "age": 30}'
python_obj = json.loads(json_data)
print(python_obj["name"])

JavaScript 中的 JSON 处理

// JavaScript 对象转 JSON 字符串
const data = {
    name: "张三",
    age: 25,
    skills: ["Python", "JavaScript", "Vue.js"]
};

const jsonString = JSON.stringify(data, null, 2);
console.log(jsonString);

// JSON 字符串转 JavaScript 对象
const jsonData = '{"name": "李四", "age": 30}';
const jsObj = JSON.parse(jsonData);
console.log(jsObj.name);

🔄 AJAX 和 Fetch API

AJAX 简介

AJAX(Asynchronous JavaScript and XML)是一种在无需重新加载整个页面的情况下,能够更新部分网页的技术。

原生 JavaScript AJAX

// 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();

// 配置请求
xhr.open('GET', '/api/users', true);

// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json');

// 处理响应
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            const response = JSON.parse(xhr.responseText);
            console.log('用户列表:', response);
        } else {
            console.error('请求失败:', xhr.status);
        }
    }
};

// 发送请求
xhr.send();

// POST 请求示例
const xhrPost = new XMLHttpRequest();
xhrPost.open('POST', '/api/users', true);
xhrPost.setRequestHeader('Content-Type', 'application/json');

xhrPost.onreadystatechange = function() {
    if (xhrPost.readyState === 4 && xhrPost.status === 201) {
        const newUser = JSON.parse(xhrPost.responseText);
        console.log('新用户:', newUser);
    }
};

const userData = {
    name: "王五",
    email: "wangwu@example.com"
};

xhrPost.send(JSON.stringify(userData));

Fetch API

Fetch API 是现代浏览器提供的更简洁的 HTTP 请求接口。

// GET 请求
fetch('/api/users')
    .then(response => {
        if (!response.ok) {
            throw new Error('网络响应错误');
        }
        return response.json();
    })
    .then(data => {
        console.log('用户列表:', data);
    })
    .catch(error => {
        console.error('请求失败:', error);
    });

// POST 请求
const userData = {
    name: "王五",
    email: "wangwu@example.com"
};

fetch('/api/users', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData)
})
.then(response => response.json())
.then(data => {
    console.log('新用户:', data);
})
.catch(error => {
    console.error('请求失败:', error);
});

// 使用 async/await
async function fetchUsers() {
    try {
        const response = await fetch('/api/users');
        if (!response.ok) {
            throw new Error('网络响应错误');
        }
        const users = await response.json();
        console.log('用户列表:', users);
        return users;
    } catch (error) {
        console.error('请求失败:', error);
    }
}

async function createUser(userData) {
    try {
        const response = await fetch('/api/users', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(userData)
        });
        
        if (!response.ok) {
            throw new Error('创建用户失败');
        }
        
        const newUser = await response.json();
        console.log('新用户:', newUser);
        return newUser;
    } catch (error) {
        console.error('请求失败:', error);
    }
}

🌐 CORS 跨域处理

什么是 CORS

CORS(Cross-Origin Resource Sharing)是一种安全机制,用于控制不同源之间的资源访问。

同源策略

同源策略要求协议、域名、端口都相同:

  • http://localhost:3000 和 http://localhost:8000 → 不同源
  • https://example.com 和 http://example.com → 不同源
  • http://api.example.com 和 http://www.example.com → 不同源

后端 CORS 配置

Flask 配置

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# 允许所有域名访问
CORS(app)

# 或者指定允许的域名
CORS(app, origins=['http://localhost:3000', 'https://example.com'])

# 或者针对特定路由
@app.route('/api/users')
@cross_origin(origins=['http://localhost:3000'])
def get_users():
    return jsonify(users)

FastAPI 配置

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 添加 CORS 中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "https://example.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

前端处理 CORS

// 在请求中添加凭证
fetch('/api/users', {
    credentials: 'include',  // 包含 cookies
    headers: {
        'Content-Type': 'application/json',
    }
});

// 使用代理解决开发环境跨域
// 在 package.json 中添加
{
  "proxy": "http://localhost:8000"
}

🔐 认证和授权

基本认证

// 在请求头中添加认证信息
fetch('/api/protected', {
    headers: {
        'Authorization': 'Bearer ' + token,
        'Content-Type': 'application/json',
    }
});

Token 管理

// 存储 token
localStorage.setItem('token', 'your-jwt-token');

// 获取 token
const token = localStorage.getItem('token');

// 删除 token
localStorage.removeItem('token');

// 检查 token 是否有效
function isTokenValid() {
    const token = localStorage.getItem('token');
    if (!token) return false;
    
    // 检查 token 是否过期
    try {
        const payload = JSON.parse(atob(token.split('.')[1]));
        return payload.exp > Date.now() / 1000;
    } catch (error) {
        return false;
    }
}

💻 实践练习

练习 1:用户管理前端

<!DOCTYPE html>
<html>
<head>
    <title>用户管理</title>
    <style>
        .user-list {
            margin: 20px 0;
        }
        .user-item {
            padding: 10px;
            border: 1px solid #ddd;
            margin: 5px 0;
        }
        .form-group {
            margin: 10px 0;
        }
        .form-group label {
            display: inline-block;
            width: 80px;
        }
    </style>
</head>
<body>
    <h1>用户管理</h1>
    
    <!-- 添加用户表单 -->
    <div>
        <h2>添加用户</h2>
        <form id="addUserForm">
            <div class="form-group">
                <label>姓名:</label>
                <input type="text" id="name" required>
            </div>
            <div class="form-group">
                <label>邮箱:</label>
                <input type="email" id="email" required>
            </div>
            <button type="submit">添加用户</button>
        </form>
    </div>
    
    <!-- 用户列表 -->
    <div>
        <h2>用户列表</h2>
        <button onclick="loadUsers()">刷新列表</button>
        <div id="userList" class="user-list"></div>
    </div>

    <script>
        const API_BASE = 'http://localhost:8000/api';
        
        // 加载用户列表
        async function loadUsers() {
            try {
                const response = await fetch(`${API_BASE}/users`);
                const users = await response.json();
                
                const userList = document.getElementById('userList');
                userList.innerHTML = users.map(user => `
                    <div class="user-item">
                        <strong>${user.name}</strong> - ${user.email}
                        <button onclick="deleteUser(${user.id})">删除</button>
                    </div>
                `).join('');
            } catch (error) {
                console.error('加载用户失败:', error);
            }
        }
        
        // 添加用户
        document.getElementById('addUserForm').addEventListener('submit', async (e) => {
            e.preventDefault();
            
            const name = document.getElementById('name').value;
            const email = document.getElementById('email').value;
            
            try {
                const response = await fetch(`${API_BASE}/users`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ name, email })
                });
                
                if (response.ok) {
                    alert('用户添加成功!');
                    document.getElementById('addUserForm').reset();
                    loadUsers();
                } else {
                    alert('添加失败');
                }
            } catch (error) {
                console.error('添加用户失败:', error);
                alert('添加失败');
            }
        });
        
        // 删除用户
        async function deleteUser(userId) {
            if (!confirm('确定要删除这个用户吗?')) return;
            
            try {
                const response = await fetch(`${API_BASE}/users/${userId}`, {
                    method: 'DELETE'
                });
                
                if (response.ok) {
                    alert('用户删除成功!');
                    loadUsers();
                } else {
                    alert('删除失败');
                }
            } catch (error) {
                console.error('删除用户失败:', error);
                alert('删除失败');
            }
        }
        
        // 页面加载时获取用户列表
        loadUsers();
    </script>
</body>
</html>

练习 2:错误处理和加载状态

// 统一的 API 请求函数
class ApiClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        const config = {
            headers: {
                'Content-Type': 'application/json',
                ...options.headers
            },
            ...options
        };
        
        try {
            const response = await fetch(url, config);
            
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            
            return await response.json();
        } catch (error) {
            console.error('API 请求失败:', error);
            throw error;
        }
    }
    
    async get(endpoint) {
        return this.request(endpoint);
    }
    
    async post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
    
    async put(endpoint, data) {
        return this.request(endpoint, {
            method: 'PUT',
            body: JSON.stringify(data)
        });
    }
    
    async delete(endpoint) {
        return this.request(endpoint, {
            method: 'DELETE'
        });
    }
}

// 使用示例
const api = new ApiClient('http://localhost:8000/api');

// 显示加载状态
function showLoading(elementId) {
    const element = document.getElementById(elementId);
    element.innerHTML = '<div>加载中...</div>';
}

// 显示错误信息
function showError(elementId, message) {
    const element = document.getElementById(elementId);
    element.innerHTML = `<div style="color: red;">错误: ${message}</div>`;
}

// 使用 API 客户端
async function loadUsersWithStatus() {
    const userList = document.getElementById('userList');
    
    try {
        showLoading('userList');
        const users = await api.get('/users');
        
        userList.innerHTML = users.map(user => `
            <div class="user-item">
                <strong>${user.name}</strong> - ${user.email}
                <button onclick="deleteUser(${user.id})">删除</button>
            </div>
        `).join('');
    } catch (error) {
        showError('userList', error.message);
    }
}

📚 学习资源

官方文档

推荐阅读

🔍 知识检查

完成本节学习后,请检查是否掌握:

  • 理解前后端分离架构的优势
  • 掌握 JSON 数据格式的使用
  • 能够使用 AJAX 和 Fetch API
  • 理解 CORS 跨域问题及解决方案
  • 掌握基本的认证和授权机制
  • 能够处理 API 请求的错误和加载状态

上一节:1.2 Web 框架入门 | 下一节:1.4 实践项目