7:概念补充
当你做到这道题时,说明你对Java语言已经有了一定的了解,不过,一个优秀的后端工程师,还需要掌握更多系统通识性知识。在真实的后端开发里,光会写业务代码连入门都算不上。代码只是上层逻辑,真正支撑系统跑起来的是底层的操作系统、网络协议、数据库和部署架构。
本道题的作用是让你在做后面的综合题之前,对里面用到的 Docker、Nginx、HTTP、MySQL 这些技术进行一定的了解。
注:若有学习笔记,可在交题时一并提交,可作为加分项
Part1-MySQL
Section titled “Part1-MySQL”学习资源:https://www.runoob.com/mysql/mysql-tutorial.html https://www.mysqltutorial.org/
在软件开发过程中,数据是系统运行的重要基础。
例如,一个校园系统需要保存学生信息,一个论坛需要保存用户和帖子,一个商城需要保存商品和订单。随着系统规模扩大,单纯使用文件保存数据会逐渐暴露出问题:
- 数据查询效率低;
- 数据结构难以维护;
- 多人同时操作时容易出现数据不一致。
因此,实际项目中通常会使用数据库来存储和管理数据。
MySQL 是目前应用较广泛的关系型数据库之一。它使用数据表组织信息,并通过 SQL 语句完成数据的增删改查。
例如,一个学生信息表可能包含:
students
id name age1 张三 202 李四 1其中每一行代表一条记录,每一列代表数据的一个属性。
Task1:设计书籍信息数据库
Section titled “Task1:设计书籍信息数据库”现在需要你为一个校园图书管理系统设计数据库。
系统主要功能为保存书籍的信息:
请完成:
- 创建一个数据库;
- 请根据需求,思考这个数据库需要设计哪些字段,选择合适的数据类型,创建一个合适的信息表;
- 插入至少 3 条任意书籍数据。
Task2:完成基本数据操作
Section titled “Task2:完成基本数据操作”创建完成后,完成以下操作:
- 查询所有书籍信息;
- 查询指定书籍的信息;
- 修改某个书籍的信息;
- 删除指定书籍的数据。
Task3:理解数据库设计
Section titled “Task3:理解数据库设计”在实际项目中,数据库设计会影响后续开发。
思考:
- 你是否选择了某个字段作为唯一标识?为什么选择该字段作为唯一标识?
- 如果存在两本书籍名称相同的情况,数据库应该如何区分这两条记录?
- 提交上述操作的运行截图
- 附上你自己对 Mysql 本身特点的理解(比如:他的优点在哪?他有没有缺点?什么地方适合使用…)
Part2-Docker
Section titled “Part2-Docker”学习资源: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:17COPY target/app.jar app.jarCMD ["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,试试运行下面的代码:
- docker --version 看看装好了没
- docker run hello-world,看到 Hello from Docker! 就算通了
- docker run -d -p 8080:80 nginx:alpine,浏览器打开 http://localhost:8080,能看到 nginx 欢迎页
- docker ps 看运行中的容器
- docker stop <容器ID> 停掉它,docker ps -a 看所有容器的状态(包括已经停止的)
- 用自己的话解释一下:镜像和容器到底有什么区别
项目组正在开发一套用户系统,需要你在本地快速搭建一个测试用的 MySQL 数据库。为了防止“在谁电脑上都能跑,一落到服务器就建表报错”的尴尬情况,团队要求必须统一使用 Docker 进行标准化部署,并且要保证测试数据在容器重启或重建后不丢失。
你需要:
-
获取与部署:拉取 mysql:8.0 镜像,以后台模式运行容器。将数据库 root 密码设为 123456,创建一个名为 user_db 的默认数据库,并将宿主机的 3306 端口映射出来供本地开发工具连接。
-
数据持久化:将 MySQL 容器内的数据目录(/var/lib/mysql)挂载到宿主机的 ~/mysql_data 目录,确保容器销毁后数据依然完好。
-
连通验证与落库:进入容器内部调用 mysql 命令行客户端,在 user_db 下创建一张 users 表并插入一条测试数据。
-
删除验证:直接使用 docker rm -f 强行删掉该容器。随后使用一模一样的挂载参数拉起一个新的 MySQL 容器,验证刚才插入的数据是否依然存在。
- 提交上述操作的运行截图并总结
- 总结你在哪个环节出了最多的问题,如果你是出题人,你会怎么去考察学习者对docker的掌握程度?
Part3-Nginx
Section titled “Part3-Nginx”学习资源: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 里的”容器间用服务名通信”吗?
Practice:
Section titled “Practice:”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 ____;}}请补全配置,使浏览器访问对应地址时能够显示网页。
- 请提交你的配置文件代码,运行截图
Part4-HTTP
Section titled “Part4-HTTP”你在浏览网页时,可能会发现一个现象:
当你打开一个网站、点击登录按钮、提交评论时,浏览器并不是直接“打开”服务器上的内容,而是向服务器发送一条请求,服务器处理后再返回结果。
那么,浏览器和服务器之间到底是如何交流的?
HTTP(HyperText Transfer Protocol)就是它们之间沟通的规则。一次 HTTP 请求中包含了请求方法、请求地址、Header、请求体等信息,服务器会根据这些内容判断客户端想做什么。
例如:
你访问一个网页:
GET /index.html
实际上是在告诉服务器:
请把 index.html 的内容返回给我。
而当你登录一个网站时:
POST /login则是在告诉服务器:
我有一些数据需要提交给你,请帮我处理。
在这个过程中,你会遇到各种不同的 HTTP 状态码,它们用于表示服务器对请求的处理结果。比如:
回答:
- HTTP 是什么?解决了什么问题?
- 请你对常见的状态码进行了解并进行总结
下面,请选择以下其中一种工具来完成后续任务:
(题目以Postman为例)
- 方案 A:Apifox / Postman
- 方案 B:终端 cURL 命令行
Task1:观察一次 HTTP 请求
Section titled “Task1:观察一次 HTTP 请求”为了更直观地理解 HTTP,请启动浏览器并打开浏览器开发者工具(Ctrl+Shift+I/F12)中的 Network 面板。 接着访问:https://www.baidu.com 观察浏览器发送的请求。
观察浏览器向服务器发送请求时包含了哪些信息,以及服务器返回响应时包含了哪些内容。 思考:客户端与服务器之间的数据交换是否需要遵循某种统一规范? 尝试找到:
- 请求使用的 Method 是什么?
- 请求 Header 中有哪些信息?
- 服务器返回了什么内容?
- 一次 HTTP 请求通常包含哪些部分?分别有什么作用?
- 请了解并总结常见 HTTP 请求方法,以及它们通常用于什么场景。
Task2:修改请求 Header
Section titled “Task2:修改请求 Header”HTTP 请求除了请求路径之外,还可以携带一些额外信息,这些信息存放在 Header(请求头) 中。
例如:
User-Agent: Chrome表示当前发送请求的客户端类型。
现在请使用 Postman 向服务器发送一个自定义 Header:
Student-Name: your-name其中 your-name 替换为你的真实姓名拼音或学号。
- 打开 Postman,新建一个 HTTP 请求。
- 根据 Task1 中的服务器地址填写请求 URL。
- 请求方法保持与 Task1 一致(不要自行修改 GET/POST)。
- 点击请求编辑区域中的 Headers 标签。
- 添加一条新的 Header:
| Key | Value |
|---|---|
| Student-Name | your name |
示例:
Student-Name: zhangsan发送请求,观察服务器返回结果。
- 为什么 HTTP 请求需要 Header?
- 如果 HTTP 请求没有 Header,服务器还能正常处理所有请求吗?有没有例外情况?
- 除了 Student-Name 外,你还能想到哪些常见 Header?它们分别有什么作用
Task3:发送 POST 请求提交数据
Section titled “Task3:发送 POST 请求提交数据”前面的任务中,我们主要通过 HTTP 请求获取数据。
但是在实际开发中,很多操作都需要向服务器提交数据,例如:
- 用户注册提交账号密码
- 用户登录提交身份信息
- 发布文章提交文章内容
- 上传表单提交用户信息
这些场景通常使用 POST 请求。
现在请使用 Postman 向以下地址发送 POST 请求:
https://httpbin.org/post服务器会返回你发送的请求内容,用于帮助你观察 HTTP 请求的数据传输过程。
Task3-1:发送普通文本
Section titled “Task3-1:发送普通文本”- 在 Postman 中创建新的请求。
- 请求方法选择:
POST- 输入请求地址:
https://httpbin.org/post- 点击 Body 标签。
- 选择:
raw并将格式设置为:
Text- 输入以下内容:
hello http- 点击 Send,观察服务器返回结果。
Task3-2:发送 JSON 数据
Section titled “Task3-2:发送 JSON 数据”HTTP 中经常使用 JSON 格式传输结构化数据。
请再次发送 POST 请求,并提交以下 JSON:
{ "username": "Tom", "age": 18}操作要求:
- 在 Body 中选择:
raw- 格式选择:
JSON- 查看 Headers 是否自动添加:
Content-Type: application/json如果没有,请手动添加:
| Key | Value |
|---|---|
| Content-Type | application/json |
- 点击 Send,观察服务器返回结果。
- 为什么提交数据时通常使用 POST 请求,而不是 GET 请求?
- 为什么发送 JSON 数据时需要设置:
Content-Type: application/json- 如果不设置 Content-Type,服务器还能收到数据吗?还能正确解析数据吗?
Task4:模拟一个简单登录请求
Section titled “Task4:模拟一个简单登录请求”-
在实际开发中,用户登录时,前端需要将用户输入的账号和密码通过 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 替换为你的真实姓名拼音或学号。
发送请求后,观察服务器返回结果,并截图保存。
- 为什么登录请求通常使用 POST,而不是 GET?
- 为什么需要设置 Content-Type: application/json?
- 用户名和密码是否应该直接放在 URL 中?为什么?
本题提交要求:
请将以下内容整理为 Markdown 笔记提交:
- 任务截图(必须包含以下两张截图):
- 请求配置截图:需完整清晰地看到 请求 URL、Method、Headers(含 Content-Type 与 X-Student-Name)以及 Body 数据。
- 返回结果截图:需完整清晰地看到 状态码 (200 OK) 以及 服务器返回的 Response 内容。
- 问题解答:回答各个任务中的思考题。
拓展思考题:
Section titled “拓展思考题:”
你在某网站点击“获取短信验证码”后,因为网络卡顿页面没有反应,你连续快速点击了 5 次按钮。几秒钟后收到 5 条验证码,但只有最后一次验证码有效,导致用户无法判断应该输入哪个验证码。现在回答下面的问题:
为什么“获取验证码”通常使用的是 POST 请求而不是 GET 请求?
如果用户多次重复发送同一个 HTTP 请求,为什么 GET 请求通常不会造成问题,而 POST 请求可能会产生问题?
后端可以采用什么手段防止这种因为 HTTP 请求重复发送带来的问题?
在你在某网页提交报名表时,由于网络问题网页没有响应,此时你点击刷新,看到了浏览器跳出的弹窗警告:确认重新提交表单 / Confirm Form Resubmission,请问为什么需要如此设计?
一个网页上的图片、CSS、JavaScript 文件,是不是也通过 HTTP 请求获取的?
通过这一系列任务,你已经不再只是关注代码本身,而是开始尝试理解一个软件系统是如何真正运行起来的。
从 HTTP 请求与接口设计,到数据库存储;从 Docker 环境部署,到 Nginx 服务配置;从代码管理到问题排查,这些看似独立的知识,实际上共同组成了后端开发的基础体系。
希望你保持好奇心,在不断实践和探索中深入理解软件系统的运行方式。从写下第一行代码,到构建一个真正能够稳定运行的系统,每一步积累都会成为你成长为后端开发者的基础。
本题提交方式
Section titled “本题提交方式”出题人联系方式
Section titled “出题人联系方式”出题人:
王府井
QQ:3624089541