深入理解前后端分离

最基础的前后端分离:一个文件夹,两个文件

忘掉 Vue、忘掉框架。只有两个文件:一个 HTML,一个 PHP。放在同一个文件夹,用同一个域名 127.0.0.1 访问。


一、准备:建一个空文件夹

mkdir /media/HUAWEI/D/wwwroot/demo
cd /media/HUAWEI/D/wwwroot/demo

这个文件夹里什么都是空的。 接下来只建两个文件。


二、文件 1:index.html(前端页面)

为什么叫 index.html?

因为浏览器访问一个目录时,默认找 index.html 或 index.php。所以 http://127.0.0.1:8000/ 会自动打开这个文件。

为什么是 .html 不是 .php?

这个文件不需要 PHP 处理,它就是纯静态页面。浏览器下载后直接渲染,PHP 服务器不需要碰它。

手动新建 index.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>最基础的前后端分离</title>
</head>
<body>
    <h1>登录</h1>

    <p>
        用户名:<input type="text" id="username" value="admin">
    </p>
    <p>
        密码:<input type="password" id="password" value="123456">
    </p>

    <button id="btn">登录</button>
    <p id="result"></p>

    <script>
        document.getElementById('btn').addEventListener('click', async () => {
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;

            const res = await fetch('login.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username: username, password: password })
            });

            const data = await res.json();

            const result = document.getElementById('result');
            if (data.code === 200) {
                result.textContent = '登录成功!你好,' + data.nickname;
                result.style.color = 'green';
            } else {
                result.textContent = data.msg;
                result.style.color = 'red';
            }
        });
    </script>
</body>
</html>

逐行讲解

<!DOCTYPE html>

告诉浏览器:这是 HTML5 文件。

<html lang="zh-CN">

lang 是语言,zh-CN 是简体中文。方便浏览器选择字体、辅助工具读屏。

<meta charset="UTF-8">

字符编码。不加这行,中文可能显示成乱码。

<input type="text" id="username" value="admin">
  • type="text":文本框
  • id="username":给 JS 定位用。JS 通过 document.getElementById('username') 拿到这个元素
  • value="admin":默认值
<input type="password" id="password" value="123456">

type="password":输入内容显示成圆点。

<button id="btn">登录</button>

给这个按钮一个 id="btn",JS 才能找到它。

<p id="result"></p>

一个空段落。JS 会往里面填结果。

下面是 JS 部分,逐行看:

document.getElementById('btn').addEventListener('click', async () => {
  • document.getElementById('btn'):从页面上找到 id="btn" 的按钮
  • .addEventListener('click', ...):监听「点击」事件,点击时执行后面的函数
  • async () => {}:异步箭头函数。因为里面要用 await
    const username = document.getElementById('username').value;
    const password = document.getElementById('password').value;
  • 从两个输入框里读出用户输入的值
  • .value 是输入框的当前内容
    const res = await fetch('login.php', {
  • 核心行。fetch 是浏览器自带的函数,用来发 HTTP 请求
  • 'login.php' 是相对路径。因为当前页面是 http://127.0.0.1:8000/index.html,所以浏览器会发到 http://127.0.0.1:8000/login.php
  • await 等请求完成
  • res 是服务器返回的响应对象
        method: 'POST',

请求方法用 POST。不写默认是 GET。因为要提交数据,所以用 POST。

        headers: { 'Content-Type': 'application/json' },

请求头。告诉服务器:「我发过来的请求体是 JSON 格式」。

这一行非常关键,它决定了 PHP 那边怎么读数据。

        body: JSON.stringify({ username: username, password: password })
  • JSON.stringify 把 JS 对象转成字符串
  • { username: 'admin', password: '123456' } 变成 '{"username":"admin","password":"123456"}'
  • body 必须是字符串,不能直接传对象
    });

请求配置结束。

    const data = await res.json();
  • res.json() 把响应体(字符串)解析成 JS 对象
  • 内部做的其实是 JSON.parse(响应体字符串)
  • await 因为 res.json() 也是异步的
    const result = document.getElementById('result');

拿到页面上那个空段落,准备往里面填内容。

    if (data.code === 200) {
        result.textContent = '登录成功!你好,' + data.nickname;
        result.style.color = 'green';
    } else {
        result.textContent = data.msg;
        result.style.color = 'red';
    }

根据服务器返回的结果,显示不同文字。


三、文件 2:login.php(后端接口)

为什么叫 login.php?

因为 index.html 里的 fetch('login.php', ...) 就是发的这个名字。名字必须对上。

为什么是 .php 不是 .html?

因为这个文件需要 PHP 处理(读请求、查数据、返回结果)。它不输出 HTML,只输出 JSON。

手动新建 login.php:

<?php

// 告诉浏览器:我返回的是 JSON,不是 HTML
header('Content-Type: application/json; charset=utf-8');

// 读原始请求体(就是 fetch 里 body 那段字符串)
$raw = file_get_contents('php://input');

// 把 JSON 字符串转成 PHP 数组
$input = json_decode($raw, true);

// 拿出用户名和密码
$username = $input['username'] ?? '';
$password = $input['password'] ?? '';

// 假装查数据库(真实场景用 PDO 查 MySQL)
if ($username === 'admin' && $password === '123456') {
    echo json_encode([
        'code' => 200,
        'nickname' => '管理员'
    ], JSON_UNESCAPED_UNICODE);
} else {
    echo json_encode([
        'code' => 401,
        'msg' => '用户名或密码错误'
    ], JSON_UNESCAPED_UNICODE);
}

逐行讲解

<?php

告诉 PHP 服务器:这行之后是 PHP 代码。

header('Content-Type: application/json; charset=utf-8');
  • 告诉浏览器:响应体是 JSON 格式,编码是 UTF-8。
  • 不加这行,浏览器会当成 HTML 处理,可能出现解析问题。
  • charset=utf-8 保证中文不乱码。
$raw = file_get_contents('php://input');
  • php://input 是 PHP 的原始请求体流。
  • file_get_contents 是「读取文件内容」的函数,php://input 被当成一个特殊文件。
  • $raw 是字符串:'{"username":"admin","password":"123456"}'

关键理解: 为什么不用 $_POST?

  • $_POST 只对 Content-Type: application/x-www-form-urlencoded 的请求自动填充。
  • 我们这里发的 Content-Type: application/json,PHP 不认识 JSON,所以 $_POST 是空的。
  • 必须自己读 php://input。
$input = json_decode($raw, true);
  • json_decode 把 JSON 字符串转成 PHP 数组
  • 第二个参数 true 表示「返回数组」,不写返回对象
  • $input 现在是:['username' => 'admin', 'password' => '123456']
$username = $input['username'] ?? '';
$password = $input['password'] ?? '';
  • ?? 是「空合并运算符」:如果键不存在,用 '' 代替。
  • 防止 PHP 报「未定义键」的警告。
if ($username === 'admin' && $password === '123456') {
  • 判断用户名密码。真实场景这里应该查数据库。
  • === 全等,类型和值都要一样。
    echo json_encode([
        'code' => 200,
        'nickname' => '管理员'
    ], JSON_UNESCAPED_UNICODE);
  • json_encode 把 PHP 数组转成 JSON 字符串
  • JSON_UNESCAPED_UNICODE 防止中文被转义成 \uXXXX(不加这个,'管理员' 会变成 \u7ba1\u7406\u5458)
  • echo 输出字符串,这就是响应体
} else {
    echo json_encode([
        'code' => 401,
        'msg' => '用户名或密码错误'
    ], JSON_UNESCAPED_UNICODE);
}

失败时返回错误信息。


四、两个文件放在一起

/media/HUAWEI/D/wwwroot/demo/
├── index.html    ← 前端页面
└── login.php     ← 后端接口

就这么简单。


五、启动服务器

在 demo 文件夹里执行:

cd /media/HUAWEI/D/wwwroot/demo
php -S 127.0.0.1:8000

注意:这次没有 -t public。 因为 index.html 和 login.php 直接在 demo/ 里,demo/ 就是网站根目录。

终端会显示:

PHP 8.x.x Development Server (http://127.0.0.1:8000) started

六、访问测试

打开浏览器,访问:

http://127.0.0.1:8000/

会发生什么?

  1. 浏览器请求 http://127.0.0.1:8000/
  2. PHP 服务器发现根目录有 index.html,直接把它返回(不需要 PHP 处理)
  3. 浏览器渲染页面,显示登录表单

点「登录」按钮:

  1. JS 读输入框的值
  2. fetch('login.php', ...) 发 POST 到 http://127.0.0.1:8000/login.php
  3. PHP 服务器发现是 .php 文件,调用 PHP 解释器执行
  4. login.php 读出 JSON 请求体,比对用户名密码
  5. 返回 JSON 响应
  6. 浏览器收到响应,res.json() 解析
  7. 页面显示「登录成功!你好,管理员」

七、用 F12 看整个过程

按 F12 → Network 面板。点登录按钮。

你会看到一个叫 login.php 的请求。 点开它:

Headers 标签:

Request URL: http://127.0.0.1:8000/login.php
Request Method: POST
Status Code: 200
Content-Type: application/json

Payload 标签(你发给服务器的):

{"username":"admin","password":"123456"}

Response 标签(服务器返回给你的):

{"code":200,"nickname":"管理员"}

这三块内容,就是整个通信过程的核心。

  • Request URL:请求地址
  • Request Method:POST
  • Content-Type:请求体是什么格式
  • Payload:请求体内容
  • Status Code:响应状态
  • Response:响应体内容

八、为什么这叫「前后端分离」?

同一个域名 127.0.0.1:8000,但两个文件各管各的:

index.html login.php
管什么 页面显示 数据处理
返回什么 HTML JSON
谁来处理 浏览器 PHP
谁调谁 主动发请求 被动响应

核心:

  • 页面和数据分开。页面只管展示,数据由 PHP 提供。
  • 通过 HTTP 通信。HTML 里的 fetch 发请求,PHP 返回 JSON。
  • 同一个域。都是 127.0.0.1:8000,所以不需要处理跨域。

这就是最小化的前后端分离。


九、把它和 Vue 项目对照

Vue 项目其实就是这样:

这个 demo Vue 项目
index.html src/views/Login.vue
login.php backend/public/index.php + AuthController.php
fetch('login.php') http.post('/auth/login')
JSON.stringify({...}) request.ts 里 JSON.stringify(options.body)
res.json() request.ts 里 return res.json()
data.code === 200 userStore.login 里的判断
同一个域名 127.0.0.1:8000 开发时 Vite 代理到 127.0.0.1:8000,生产时 Nginx 同域

区别只有:

  • Vue 把页面拆成了组件(Login.vue、Dashboard.vue)
  • Vue 用路由做页面跳转(router.push)
  • Vue 用 Pinia 存登录状态
  • Vue 用 Vite 编译 .vue/.ts/.scss

但 HTTP 通信的部分完全一样:还是 fetch,还是 POST,还是 JSON。


十、总结

一个文件夹、两个文件,就是最小化前后端分离:

demo/
├── index.html    ← 前端:显示页面 + 发请求
└── login.php     ← 后端:收请求 + 返回数据

通信过程:

浏览器打开 index.html
      ↓
用户点击「登录」
      ↓
JS 发 POST 到 login.php
      ↓
PHP 收到 JSON,处理,返回 JSON
      ↓
JS 收到 JSON,更新页面

核心知识点:

  • fetch 发请求
  • JSON.stringify 对象 → 字符串
  • file_get_contents('php://input') 读原始请求体
  • json_decode 字符串 → PHP 数组
  • json_encode PHP 数组 → 字符串
  • res.json() 字符串 → JS 对象

理解了这个 demo,就理解了 Vue 项目的所有通信逻辑。 Vue 只是把 index.html 拆成组件、把 login.php 拆成控制器,本质没变。

posted @ 2026-10-13 12:10  华腾智算  阅读(10)  评论(0)    收藏  举报
https://damo.alibaba.com/ https://tianchi.aliyun.com/course?spm=5176.21206777.J_3941670930.5.87dc17c9BZNvLL