跳转到内容

7:概念补充

后端Banner

当你做到这道题时,说明你对Java语言已经有了一定的了解,不过,一个优秀的后端工程师,还需要掌握更多系统通识性知识。在真实的后端开发里,光会写业务代码连入门都算不上。代码只是上层逻辑,真正支撑系统跑起来的是底层的操作系统、网络协议、数据库和部署架构。

本道题的作用是让你在做后面的综合题之前,对里面用到的 Docker、Nginx、HTTP、MySQL 这些技术进行一定的了解。

注:若有学习笔记,可在交题时一并提交,可作为加分项


学习资源:https://www.runoob.com/mysql/mysql-tutorial.html https://www.mysqltutorial.org/

在软件开发过程中,数据是系统运行的重要基础。

例如,一个校园系统需要保存学生信息,一个论坛需要保存用户和帖子,一个商城需要保存商品和订单。随着系统规模扩大,单纯使用文件保存数据会逐渐暴露出问题:

  • 数据查询效率低;
  • 数据结构难以维护;
  • 多人同时操作时容易出现数据不一致。

因此,实际项目中通常会使用数据库来存储和管理数据。

MySQL 是目前应用较广泛的关系型数据库之一。它使用数据表组织信息,并通过 SQL 语句完成数据的增删改查。

例如,一个学生信息表可能包含:

students
id name age
1 张三 20
2 李四 1

其中每一行代表一条记录,每一列代表数据的一个属性。

7-1

现在需要你为一个校园图书管理系统设计数据库。

系统主要功能为保存书籍的信息:

请完成:

  1. 创建一个数据库;
  2. 请根据需求,思考这个数据库需要设计哪些字段,选择合适的数据类型,创建一个合适的信息表;
  3. 插入至少 3 条任意书籍数据。

创建完成后,完成以下操作:

  1. 查询所有书籍信息;
  2. 查询指定书籍的信息;
  3. 修改某个书籍的信息;
  4. 删除指定书籍的数据。

在实际项目中,数据库设计会影响后续开发。

思考:

  1. 你是否选择了某个字段作为唯一标识?为什么选择该字段作为唯一标识?
  2. 如果存在两本书籍名称相同的情况,数据库应该如何区分这两条记录?
  • 提交上述操作的运行截图
  • 附上你自己对 Mysql 本身特点的理解(比如:他的优点在哪?他有没有缺点?什么地方适合使用…)

学习资源:https://www.runoob.com/docker/docker-tutorial.html https://docs.docker.com/get-started/ https://www.itbaima.cn/en-US/document/zj9uvg0sp3b0sok8

不知道你有没有遇到过这种情况:你在自己电脑上写了个 Java 程序,跑得好好的,发给别人就跑不起来了—种种问题类似:“我这是 Mac 系统跟你不一样”、“我 JDK 版本跟你不一样”、“我 Windows 路径写法跟你 Linux 不一样”。这就是环境不一致带来的问题。

Docker 技术就是为了解决这个而生的。它把你的程序和它需要的一切环境(JDK、配置文件、依赖)打包到一个叫”镜像”的东西里,只要 CPU 架构相同,拿到任何装了 Docker 的机器上都能直接运行。

你可能想问:那跟我用虚拟机有啥区别?虚拟机是在你的系统里完整模拟一台电脑,包括它的整个操作系统,占几个 G 的内存,启动要好几分钟。Docker 共享宿主机的 Linux 内核,不需要在容器内自带独立的 内核,因而极其轻量。你后面调试项目的时候动不动就要重启容器,如果用虚拟机,等开机都能等到你崩溃。

下面是几个基础但重要的概念:

镜像(Image):一个只读的模板,里面装好了操作系统、软件、代码、配置,是”静止的”。比如 mysql:8.0 这个镜像,里面包含运行 MySQL 8.0 所需的最小化运行依赖、动态库和配置文件。

容器(Container):镜像跑起来之后的运行实例,是”活动的”。同一个镜像可以启动多个容器,互相隔离,谁也看不到谁的数据。用面向对象的说法来类比:镜像 = 类,容器 = 对象。

Dockerfile:一个文本文件,告诉 Docker 怎么一步一步构建你的镜像。比如:

FROM openjdk:17
COPY target/app.jar app.jar
CMD ["java", "-jar", "app.jar"]

基于 openjdk:17 基础镜像,把编译好的 jar 包复制进去,并指定默认启动命令。像 RUN、COPY 这类指令会产生单独的镜像层,Docker 构建时会严格按顺序对比每一层的缓存。只要某一层(比如复制代码)内容变了,它以及它后面的所有指令层都必须重新构建。所以构建优化的一条铁律是:把很少变动的指令(如安装系统依赖)写在前面,把频繁变动的指令(如复制代码)写在后面。

docker-compose.yml:当你的项目需要同时运行 MySQL、Java 后端、Nginx 多个容器时,手动敲 docker run 挂载参数不仅效率低且极易出错。Docker Compose 让你在一个 YAML 文件里集中定义所有容器的配置:镜像来源、端口映射、环境变量以及容器依赖关系(depends_on)。配置完成后,只需一条 docker compose up -d 就能按顺序启动整套服务架构。

最容易出问题的地方—网络

每个容器内部都是独立的网络隔离空间,都有自己的 localhost。如果你在 Java 容器的代码里把数据库连接地址写成 localhost:3306,Java 会去连接自己容器内的 3306 端口,而 MySQL 运行在另一个独立的容器里,导致连接直接失败。 在 Docker Compose 中,默认会为当前配置下的所有容器创建一个共享的虚拟网络。容器之间不再依靠 IP 通信,而是直接通过服务名(service name)进行 DNS 解析。如果你的 MySQL 服务名定义为 mysql,那么 Java 的数据库连接地址必须写成:

jdbc:mysql://mysql:3306/board

这里的 mysql 就是 docker-compose.yml 里 MySQL 服务的名字。不是 localhost,也不是 IP 地址。很多新手第一次都会在这里卡住,把 localhost 改成服务名就好了。


可能会遇到的问题:

  • 启动顺序问题:MySQL 容器启动之后,MySQL 服务本身还需要几秒才能开始接受连接(它要初始化数据、建表)。Java 后端启动很快,可能 MySQL 还没就绪它就开始尝试连接了,这时候会报 Connection refused。解决方法是给 MySQL 加健康检查(healthcheck),然后在 Java 的 depends_on 里配置 condition: service_healthy,让 Docker 等 MySQL 健康了再启动 Java。

  • 路径问题:Dockerfile 里的 COPY、compose 文件里的 volumes,路径都是相对于当前文件所在目录的。比如在 docker-compose.yml 里写 ./nginx/nginx.conf:/etc/nginx/nginx.conf,前面的路径相对于 docker-compose.yml。写错了就会报 no such file or directory。看到这个报错先检查路径。


常用命令(仅举例)

docker compose up -d # 启动所有容器(-d 后台运行)
docker compose down # 停掉所有容器
docker compose build --no-cache # 重新构建镜像
docker compose logs <服务名> # 看日志——排查第一工具
docker ps -a # 看所有容器的运行状态
docker exec -it <容器名> sh # 进入容器内部

建议去动手练一练,背指令的学习方式效率相对低下,在实际问题中应用,这样学起来更有成就感,也相对不会那么枯燥,加油!


在Linux系统里安装Docker,试试运行下面的代码:

  1. docker --version 看看装好了没
  2. docker run hello-world,看到 Hello from Docker! 就算通了
  3. docker run -d -p 8080:80 nginx:alpine,浏览器打开 http://localhost:8080,能看到 nginx 欢迎页
  4. docker ps 看运行中的容器
  5. docker stop <容器ID> 停掉它,docker ps -a 看所有容器的状态(包括已经停止的)
  6. 用自己的话解释一下:镜像和容器到底有什么区别

项目组正在开发一套用户系统,需要你在本地快速搭建一个测试用的 MySQL 数据库。为了防止“在谁电脑上都能跑,一落到服务器就建表报错”的尴尬情况,团队要求必须统一使用 Docker 进行标准化部署,并且要保证测试数据在容器重启或重建后不丢失。

你需要:

  1. 获取与部署:拉取 mysql:8.0 镜像,以后台模式运行容器。将数据库 root 密码设为 123456,创建一个名为 user_db 的默认数据库,并将宿主机的 3306 端口映射出来供本地开发工具连接。

  2. 数据持久化:将 MySQL 容器内的数据目录(/var/lib/mysql)挂载到宿主机的 ~/mysql_data 目录,确保容器销毁后数据依然完好。

  3. 连通验证与落库:进入容器内部调用 mysql 命令行客户端,在 user_db 下创建一张 users 表并插入一条测试数据。

  4. 删除验证:直接使用 docker rm -f 强行删掉该容器。随后使用一模一样的挂载参数拉起一个新的 MySQL 容器,验证刚才插入的数据是否依然存在。

  • 提交上述操作的运行截图并总结
  • 总结你在哪个环节出了最多的问题,如果你是出题人,你会怎么去考察学习者对docker的掌握程度?

学习资源:https://nginx.org/cn/docs/ https://github.com/dunwu/nginx-tutorial

为什么需要 Nginx?

在刚开始学习 Web 开发时,一个项目可能只有一个服务器:

用户
后端服务器

所有请求都直接发送给后端程序,看起来非常简单。

但是随着用户数量增加,一个服务器可能会逐渐承担越来越大的压力。例如同时有大量用户访问网站,服务器可能出现响应变慢甚至无法访问的情况。这个时候,单独依靠一台服务器已经难以满足需求。

一种常见的解决方式是增加服务器数量,让多个后端程序一起工作:

后端服务器1
/
用户 → Nginx → 后端服务器2
\
后端服务器3

但是新的问题又出现了:用户并不知道应该访问哪一台服务器,也不应该由用户自己决定请求发送到哪里。这时就需要一个中间角色来帮助管理请求。

Nginx 就承担了这个角色。

它可以接收用户请求,根据配置将请求转发给不同的后端服务器,这个过程叫做反向代理。同时,Nginx 还可以根据服务器当前压力分配请求,让多个服务器共同承担访问压力,这就是负载均衡

除了转发请求,Nginx 还可以处理静态资源,例如网页中的图片、CSS、JavaScript 文件等。相比让后端程序处理这些简单文件,Nginx 可以更加高效地完成这些工作。

简单来说:

用户只需要访问 Nginx,而 Nginx 负责管理后面的服务器。它就像网站的“前台接待”,先接收所有访问请求,再根据情况安排给合适的后端服务。

Nginx 的配置写在 nginx.conf 里,核心结构如下:

server {
listen 80;
location / {
root /usr/share/nginx/html;
}
location /api/ {
proxy_pass http://backend:8080;
}
}

server 块定义一个虚拟服务器。listen 80 表示监听 80 端口。

location 块按路径匹配请求。location / 匹配所有请求,location /api/ 只匹配以 /api/ 开头的请求。Nginx 的匹配规则是最长匹配前缀优先——/api/messages 会命中 /api/ 而不是 /,因为 /api/ 更精确。

proxy_pass 是反向代理的核心指令。http://backend:8080 告诉 Nginx 把请求转发到这个地址。这里的 backend 是 docker-compose.yml 里 Java 后端的服务名——还记得 Docker 里的”容器间用服务名通信”吗?

Nginx 是一个常用的 Web 服务器,可以帮助我们向用户返回网页文件。

现在需要你自己创建一个HTML文件到某位置并命名为index.html,并写入以下内容:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Beautiful Pineapple</title>
<style>
body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: radial-gradient(circle, #fffdf0 0%, #ffeaa7 100%);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.card {
display: flex;
flex-direction: column;
align-items: center;
padding: 50px 60px;
background: rgba(255, 255, 255, 0.65);
backdrop-filter: blur(12px);
border-radius: 24px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.icon {
font-size: 120px;
line-height: 1;
margin-bottom: 20px;
filter: drop-shadow(0 10px 15px rgba(180, 110, 0, 0.2));
user-select: none;
}
.title {
font-size: 28px;
letter-spacing: 2px;
color: #6b4701;
font-weight: 700;
text-transform: uppercase;
}
</style>
</head>
<body>
<div class="card">
<div class="icon">🍍</div>
<div class="title">Pineapple</div>
</div>
</body>
</html>

接着,需要你自己去补全Nginx的配置文件nginx.conf,并通过 Nginx 来访问它。

Nginx 配置文件:

server {
listen ____;
location / {
root ____;
index ____;
}
}

请补全配置,使浏览器访问对应地址时能够显示网页。

  • 请提交你的配置文件代码,运行截图

你在浏览网页时,可能会发现一个现象:

当你打开一个网站、点击登录按钮、提交评论时,浏览器并不是直接“打开”服务器上的内容,而是向服务器发送一条请求,服务器处理后再返回结果。

那么,浏览器和服务器之间到底是如何交流的?

HTTP(HyperText Transfer Protocol)就是它们之间沟通的规则。一次 HTTP 请求中包含了请求方法、请求地址、Header、请求体等信息,服务器会根据这些内容判断客户端想做什么。

例如:

你访问一个网页:

GET /index.html
7-2

实际上是在告诉服务器:

请把 index.html 的内容返回给我。

而当你登录一个网站时:

POST /login

则是在告诉服务器:

我有一些数据需要提交给你,请帮我处理。

在这个过程中,你会遇到各种不同的 HTTP 状态码,它们用于表示服务器对请求的处理结果。比如:

7-3 7-4

​ 回答:

  • HTTP 是什么?解决了什么问题?
  • 请你对常见的状态码进行了解并进行总结

​ 下面,请选择以下其中一种工具来完成后续任务:

(题目以Postman为例)

  1. 方案 A:Apifox / Postman
  2. 方案 B:终端 cURL 命令行

为了更直观地理解 HTTP,请启动浏览器并打开浏览器开发者工具(Ctrl+Shift+I/F12)中的 Network 面板。 接着访问:https://www.baidu.com 观察浏览器发送的请求。

观察浏览器向服务器发送请求时包含了哪些信息,以及服务器返回响应时包含了哪些内容。 思考:客户端与服务器之间的数据交换是否需要遵循某种统一规范? 尝试找到:

  • 请求使用的 Method 是什么?
  • 请求 Header 中有哪些信息?
  • 服务器返回了什么内容?
  • 一次 HTTP 请求通常包含哪些部分?分别有什么作用?
  • 请了解并总结常见 HTTP 请求方法,以及它们通常用于什么场景。

HTTP 请求除了请求路径之外,还可以携带一些额外信息,这些信息存放在 Header(请求头) 中。

例如:

User-Agent: Chrome

表示当前发送请求的客户端类型。

现在请使用 Postman 向服务器发送一个自定义 Header:

Student-Name: your-name

其中 your-name 替换为你的真实姓名拼音或学号

  1. 打开 Postman,新建一个 HTTP 请求。
  2. 根据 Task1 中的服务器地址填写请求 URL。
  3. 请求方法保持与 Task1 一致(不要自行修改 GET/POST)。
  4. 点击请求编辑区域中的 Headers 标签。
  5. 添加一条新的 Header:
KeyValue
Student-Nameyour name

示例:

Student-Name: zhangsan

发送请求,观察服务器返回结果。


  1. 为什么 HTTP 请求需要 Header?
  2. 如果 HTTP 请求没有 Header,服务器还能正常处理所有请求吗?有没有例外情况?
  3. 除了 Student-Name 外,你还能想到哪些常见 Header?它们分别有什么作用

前面的任务中,我们主要通过 HTTP 请求获取数据

但是在实际开发中,很多操作都需要向服务器提交数据,例如:

  • 用户注册提交账号密码
  • 用户登录提交身份信息
  • 发布文章提交文章内容
  • 上传表单提交用户信息

这些场景通常使用 POST 请求

现在请使用 Postman 向以下地址发送 POST 请求:

https://httpbin.org/post

服务器会返回你发送的请求内容,用于帮助你观察 HTTP 请求的数据传输过程。


  1. 在 Postman 中创建新的请求。
  2. 请求方法选择:
POST
  1. 输入请求地址:
https://httpbin.org/post
  1. 点击 Body 标签。
  2. 选择:
raw

并将格式设置为:

Text
  1. 输入以下内容:
hello http
  1. 点击 Send,观察服务器返回结果。

HTTP 中经常使用 JSON 格式传输结构化数据。

请再次发送 POST 请求,并提交以下 JSON:

{
"username": "Tom",
"age": 18
}

操作要求:

  1. 在 Body 中选择:
raw
  1. 格式选择:
JSON
  1. 查看 Headers 是否自动添加:
Content-Type: application/json

如果没有,请手动添加:

KeyValue
Content-Typeapplication/json
  1. 点击 Send,观察服务器返回结果。

  1. 为什么提交数据时通常使用 POST 请求,而不是 GET 请求?
  2. 为什么发送 JSON 数据时需要设置:
Content-Type: application/json
  1. 如果不设置 Content-Type,服务器还能收到数据吗?还能正确解析数据吗?
  • 在实际开发中,用户登录时,前端需要将用户输入的账号和密码通过 HTTP 请求发送给服务器。

    请使用 Postman 模拟一次登录请求。

    请求地址:

    https://httpbin.org/post

    请求方法:

    POST

    用户输入:

    用户名:admin
    密码:123456

    请构造请求:

    请求体:

    {
    "username": "admin",
    "password": "123456"
    }
    • 使用 POST 方法;
    • 请求数据使用 JSON 格式;
    • 添加请求 Header:
    Content-Type: application/json

    以及 Task2 中使用的:

    X-Student-Name: your-name

    其中 your-name 替换为你的真实姓名拼音或学号。

    发送请求后,观察服务器返回结果,并截图保存。

    1. 为什么登录请求通常使用 POST,而不是 GET?
    2. 为什么需要设置 Content-Type: application/json
    3. 用户名和密码是否应该直接放在 URL 中?为什么?

本题提交要求:

请将以下内容整理为 Markdown 笔记提交:

  1. 任务截图(必须包含以下两张截图)
    • 请求配置截图:需完整清晰地看到 请求 URLMethodHeaders(含 Content-Type 与 X-Student-Name)以及 Body 数据。
    • 返回结果截图:需完整清晰地看到 状态码 (200 OK) 以及 服务器返回的 Response 内容
  2. 问题解答:回答各个任务中的思考题。
  1. 你在某网站点击“获取短信验证码”后,因为网络卡顿页面没有反应,你连续快速点击了 5 次按钮。几秒钟后收到 5 条验证码,但只有最后一次验证码有效,导致用户无法判断应该输入哪个验证码。现在回答下面的问题:

  • 为什么“获取验证码”通常使用的是 POST 请求而不是 GET 请求?

  • 如果用户多次重复发送同一个 HTTP 请求,为什么 GET 请求通常不会造成问题,而 POST 请求可能会产生问题?

  • 后端可以采用什么手段防止这种因为 HTTP 请求重复发送带来的问题?

  1. 在你在某网页提交报名表时,由于网络问题网页没有响应,此时你点击刷新,看到了浏览器跳出的弹窗警告:确认重新提交表单 / Confirm Form Resubmission,请问为什么需要如此设计?

  2. 一个网页上的图片、CSS、JavaScript 文件,是不是也通过 HTTP 请求获取的?


通过这一系列任务,你已经不再只是关注代码本身,而是开始尝试理解一个软件系统是如何真正运行起来的。

从 HTTP 请求与接口设计,到数据库存储;从 Docker 环境部署,到 Nginx 服务配置;从代码管理到问题排查,这些看似独立的知识,实际上共同组成了后端开发的基础体系。

希望你保持好奇心,在不断实践和探索中深入理解软件系统的运行方式。从写下第一行代码,到构建一个真正能够稳定运行的系统,每一步积累都会成为你成长为后端开发者的基础。

提交点这里

出题人:出题人头像  王府井

QQ:3624089541

邮箱:3624089541@qq.com