传统业务容器化

6223 字
31 分钟
传统业务容器化

传统业务容器化#

[TOC]


若依项目介绍#

==RuoYi-Vue== 是一个基于经典技术栈的Java EE企业级快速开发平台,它整合了Spring Boot、Spring Security、MyBatis、JWT和Vue等主流技术

  • 帮助开发者快速搭建稳定、安全且可扩展的企业应用系统

image-20260506191909574
image-20260506191909574

image-20260506192024225
image-20260506192024225


image-20260506135825659
image-20260506135825659

手动部署#

数据库安装#

Terminal window
1)克隆(下载)代码
[root@Docker ~]# git clone https://gitee.com/y_project/RuoYi-Vue.git
'下载的时候,也会给你创建一个目录'
[root@Docker ~]# ls
RuoYi
[root@Docker ~]# cd RuoYi-Vue/
[root@Docker RuoYi-Vue]#
[root@Docker RuoYi-Vue]# ls
bin LICENSE README.md ruoyi-common ....xxx
2)部署MySQL
# 这次使用容器
[root@Docker RuoYi-Vue]# docker container run \
-e MYSQL_ALLOW_EMPTY_PASSWORD="yes" \
-d \
-p 3306:3306 \
--name mysql-server \
-e MYSQL_DATABASE="ry-vue" \
-e MYSQL_USER="jiu" \
-e MYSQL_PASSWORD="oldboy123.com" \
mysql:8.0.36 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci \
--default-authentication-plugin=mysql_native_password
# 数据库是ry-vue
[root@Docker RuoYi-Vue]# docker ps
3c2a88c2023b mysql:8.0.36 "docker-entrypoint.s…" 3 seconds ago Up 3 seconds 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp mysql-server
[root@Docker RuoYi-Vue]# ss -lnt | grep 3306
LISTEN 0 4096 0.0.0.0:3306 0.0.0.0:*
LISTEN 0 4096 [::]:3306 [::]:*
3)导入sql语句
# 创建数据库ry-vue并导入数据脚本ry_2021xxxx.sql,quartz.sql
# 官方文档👆,但其实并没有给你具体的sql语句位置📍
[root@Docker RuoYi-Vue]# find ./ -name "*.sql"
./sql/quartz.sql
./sql/ry_20260417.sql
# 直接把 ./sql 目录拷贝至容器中
[root@Docker RuoYi-Vue]# docker cp ./sql mysql-server:/
Successfully copied 415kB (transferred 419kB) to mysql-server:/
[root@Docker RuoYi-Vue]# docker exec -it mysql-server /bin/bash
bash-4.4# mysql ry-vue < /sql/quartz.sql
bash-4.4# mysql ry-vue < /sql/ry_20260417.sql
bash-4.4# mysql
Welcome to the MySQL monitor.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| ry-vue |
| sys |
+--------------------+
mysql> use ry-vue;
mysql> show tables;
+--------------------------+
| Tables_in_ry-vue |
+--------------------------+
| QRTZ_BLOB_TRIGGERS |
| QRTZ_CALENDARS |
| QRTZ_CRON_TRIGGERS |
| xxxxx........... |
3)修改配置文件指向数据库
[root@Docker RuoYi-Vue]# vim ./ruoyi-admin/src/main/resources/application-druid.yml
url: jdbc:mysql://localhost:3306/ry-vue?use...xxx
username: jiu
password: oldboy123.com
# url不需要动,本地的3306端口,传递了一些参数
⚠️ 确保这个url是localhost:3306/ry-vue
'后面紧跟的是数据库,我们创建的是ry-vue'
# 改用户名和密码
===================================
✅️ 服务器配置
'里面有详细的备注'
[root@Docker RuoYi-Vue]# cat./ruoyi-admin/src/main/resources/application.yml
📌 开发环境配置 --> 里面也有Redis缓存的东西
server:
port: 8080
# 后端访问入口为8080
# 前端我们改为80
'各种服务的配置!非常详细'
[root@Docker RuoYi-Vue]# ss -lntup | grep 8080
⚠️ 8080端口没有被占用
===================================
'Redis缓存'
# 直接运行容器了
[root@Docker RuoYi-Vue]# docker container run -d \
--name redis-server \
-p 6379:6379 \
redis:7.2.8
[root@Docker RuoYi-Vue]# ss -lnt | grep 6379
LISTEN 0 4096 0.0.0.0:6379 0.0.0.0:*
LISTEN 0 4096 [::]:6379 [::]:*

image-20260506135521902
image-20260506135521902

JDK安装#

image-20260506153818370
image-20260506153818370

Terminal window
1)上传解压
[root@Docker RuoYi-Vue]# ls /tmp/jdk-17.0.12_linux-x64_bin.tar.gz
/tmp/jdk-17.0.12_linux-x64_bin.tar.gz
[root@Docker RuoYi-Vue]# tar xf /tmp/jdk-17.0.12_linux-x64_bin.tar.gz -C /usr/local/
[root@Docker RuoYi-Vue]# ls /usr/local/jdk-17.0.12/
bin conf
2)添加环境变量
[root@Docker RuoYi-Vue]# vim /etc/profile
# java
export JAVA_HOME=/usr/local/jdk-17.0.12
export PATH=$PATH:$JAVA_HOME/bin
[root@Docker RuoYi-Vue]# source /etc/profile
[root@Docker RuoYi-Vue]# java -version
java version "17.0.12" 2024-07-16 LTS
✅️ 可以参考下面maven环境变量的添加方式

Maven打包#

image-20260506145944102
image-20260506145944102

Terminal window
1)上传解压
[root@Docker RuoYi-Vue]# ls /tmp/apache-maven-3.9.15-bin.tar.gz
/tmp/apache-maven-3.9.15-bin.tar.gz
[root@Docker RuoYi-Vue]# tar xvf /tmp/apache-maven-3.9.15-bin.tar.gz -C /usr/local/
2)配置环境变量
[root@Docker RuoYi-Vue]# ls /usr/local/apache-maven-3.9.15/
bin boot conf lib LICENSE NOTICE README.txt
[root@Docker RuoYi-Vue]# vim /etc/profile.d/maven.sh
# 放在这个目录下,每次重新打开新的终端窗口,会被自动 source 一次
export MAVEN_HOME=/usr/local/apache-maven-3.9.15
export PATH=$PATH:$MAVEN_HOME/bin
[root@Docker RuoYi-Vue]# mvn -v
-bash: mvn: command not found
[root@Docker RuoYi-Vue]# source /etc/profile.d/maven.sh
[root@Docker RuoYi-Vue]# mvn -v
Apache Maven 3.9.15
Maven home: /usr/local/apache-maven-3.9.15
Java version: 17.0.12
/usr/local/jdk-17.0.12
3)打包编译
# 在ruoyi项目的bin目录下执行package.bat打包Web工程
# 生成war/jar包文件
".bat脚本 用于在 Windows 操作系统中运行"
# 我们是Linux操作系统
[root@Docker RuoYi-Vue]# cat ./bin/package.bat
@echo off
echo xxx.....
cd ..
call mvn clean package -Dmaven.test.skip=true
# 本质就是这条命令👆
[root@Docker RuoYi-Vue]# mvn clean package -Dmaven.test.skip=true
[INFO] --------------------
[INFO] Reactor Summary for ruoyi 3.9.2:
[INFO]
[INFO] ruoyi ............. SUCCESS [ 0.185 s]
[INFO] ruoyi-common ...... SUCCESS [ 10.544 s]
[INFO] ruoyi-system ...... SUCCESS [ 0.676 s]
[INFO] ruoyi-framework ... SUCCESS [ 9.742 s]
[INFO] ruoyi-quartz ...... SUCCESS [ 1.705 s]
[INFO] ruoyi-generator ... SUCCESS [ 1.354 s]
[INFO] ruoyi-admin ....... SUCCESS [ 11.936 s]
[INFO] -------------------
[INFO] BUILD SUCCESS
[INFO] -------------------
[INFO] Total time: 36.503 s
[INFO] Finished at: 2026-05-06T15:50:13+08:00
'都成功了'
[root@Docker RuoYi-Vue]# ls ruoyi-admin/target/
classes maven-archiver ruoyi-admin.jar
generated-sources maven-status ruoyi-admin.jar.original
✅️ '打包为jar包'
4)后台启动
[root@Docker RuoYi-Vue]# java -jar ruoyi-admin/target/ruoyi-admin.jar
(♥◠‿◠)ノ゙ 若依启动成功 (´ڡ`)゙
.-------. ____ __
| _ _ \ \ \ / /
| ( ' ) | \ _. / '
|(_ o _) / _( )_ .' '
| (_,_).' __ ___(_ o _)'
| |\ \ | || |(_,_)' '
| | \ `' /| `-' /
| | \ / \ /
''-' `'-' `-..-'
✅️ "成功启动"
# 后台运行成功!

image-20260506192501036
image-20260506192501036

前端部署#

image-20260506162739873
image-20260506162739873

Terminal window
1)下载node.js并解压
[root@Docker RuoYi-Vue]# ls /tmp/node-v24.15.0-linux-x64.tar.xz
/tmp/node-v24.15.0-linux-x64.tar.xz
[root@Docker RuoYi-Vue]# tar xf /tmp/node-v24.15.0-linux-x64.tar.xz -C /usr/local/
2)环境变量
[root@Docker RuoYi-Vue]# ls /usr/local/node-v24.15.0-linux-x64/
bin CHANGELOG.md include lib
# 我们依旧单独写一个脚本
[root@Docker RuoYi-Vue]# vim /etc/profile.d/nodejs.sh
#!/bin/bash
export NODEJS_HOME=/usr/local/node-v24.15.0-linux-x64
export PATH=$PATH:$NODEJS_HOME/bin
[root@Docker RuoYi-Vue]# node -v
-bash: node: command not found
[root@Docker RuoYi-Vue]# source /etc/profile.d/nodejs.sh
[root@Docker RuoYi-Vue]# node -v
v24.15.0
3)安装依赖并运行"前台服务"
[root@Docker RuoYi-Vue]# cd ruoyi-ui/
[root@Docker ruoyi-ui]# npm install --registry=https://registry.npmmirror.com
[root@Docker ruoyi-ui]# echo $?
0
# 运行成功
[root@Docker ruoyi-ui]# npm run dev
# 启动项目
App running at:
- Network: http://10.0.0.99:80/
访问webUI👆

image-20260506192952374
image-20260506192952374

乱码问题#

image-20260506193021572
image-20260506193021572

Terminal window
"数据库字符集有问题!"
[root@Docker ~]# docker exec -it mysql-server /bin/bash
bash-4.4# mysql
Welcome to the MySQL monitor.
mysql> SHOW VARIABLES LIKE 'character_set_server';
+----------------------+---------+
| Variable_name | Value |
+----------------------+---------+
| character_set_server | utf8mb4 |
+----------------------+---------+
1 row in set (0.01 sec)
✅️ 服务端默认就是它 --> utf8mb4
mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+-------------+
| Variable_name | Value |
+--------------------------+-------------+
| character_set_client | latin1 ❌️ |
| character_set_connection | latin1 ❌️ |
| character_set_results | latin1 ❌️ |
............xxxxxx
"主要就是这三个"
mysql> SET NAMES utf8mb4;
+--------------------------+--------
| Variable_name | Value
+--------------------------+--------
| character_set_client | utf8mb4
| character_set_connection | utf8mb4
| character_set_results | utf8mb4
'一键给这3个直接改过来'
⚠️ 上面是临时修改,不是永久性的
bash-4.4# egrep -v "^#|^$" /etc/my.cnf
[mysqld]
skip-host-cache
skip-name-resolve
datadir=/var/lib/mysql
socket=/var/run/mysqld/mysqld.sock
secure-file-priv=/var/lib/mysql-files
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[mysql]
default-character-set = utf8mb4
[client]
socket=/var/run/mysqld/mysqld.sock
default-character-set = utf8mb4
!includedir /etc/mysql/conf.d/
# 以cat > /etc/my.cnf <<EOF 追加的方式永久修改 ✅️
bash-4.4# exit
exit
[root@Docker ~]# docker restart mysql-server
[root@Docker ~]# docker exec -it mysql-server /bin/bash
bash-4.4# mysql
Welcome to the MySQL monitor.
mysql> SHOW VARIABLES LIKE 'character%';
✅️ 重启容器后,依旧生效!

image-20260506210029585
image-20260506210029585

  • 但我打开后 —> ==还是乱码==
    • 因为已经写到了数据库中
Terminal window
✅️ 删除数据库 --> 重新导入数据
[root@Docker ~]# docker exec -it mysql-server /bin/bash
bash-4.4# mysql
Welcome to the MySQL monitor
mysql> DROP DATABASE ry-vue;
'-vue' at line 1
# 因为中间有 -杠 得用反引号包裹起来
mysql> DROP DATABASE `ry-vue`;
Query OK
mysql> CREATE DATABASE `ry-vue`;
Query OK
mysql> exit
Bye
bash-4.4# mysql ry-vue < /sql/quartz.sql
bash-4.4# mysql ry-vue < /sql/ry_20260417.sql
mysql> SHOW GRANTS FOR 'jiu'@'%';
+-------------------------------------------------+
| Grants for jiu@% |
+-------------------------------------------------+
| GRANT USAGE ON *.* TO `jiu`@`%` |
| GRANT ALL PRIVILEGES ON `ry-vue`.* TO `jiu`@`%` |
+-------------------------------------------------+
# 用户jiu依旧对 `ry-vue` 表拥有权限
🌰 分别启动前后端
[root@Docker ~]# cd RuoYi-Vue/
[root@Docker RuoYi-Vue]# java -jar ruoyi-admin/target/ruoyi-admin.jar
'上面是后端,下面是运行前端'
# 分别用两个终端进行启动
[root@Docker ~]# cd RuoYi-Vue/
[root@Docker RuoYi-Vue]# cd ruoyi-ui/
[root@Docker ruoyi-ui]# npm run dev

image-20260507081924000
image-20260507081924000

项目容器化#

后端#

Terminal window
MySQL,和Redis它们都有对应的容器
流程步骤:
1.修改配置文件(MySQL,Redis数据指向)
2.Maven重新打包
3.手动测试运行jar包
# 配置JDK,拷贝jar包
Terminal window
1)修改mysql地址指向
[root@Docker RuoYi-Vue]# vim ./ruoyi-admin/src/main/resources/application-druid.yml
# 主库数据源
master:
url: jdbc:mysql://mysql-server:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: jiu
password: oldboy123.com
'后面改为容器名(域名) --> mysql-server'
# 原来是localhost
2)Redis地址指向
[root@Docker RuoYi-Vue]# vim ./ruoyi-admin/src/main/resources/application.yml
# redis 配置
redis:
# 地址
host: redis-server
# 端口,默认为6379
port: 6379
'主机改为redis-server'
3)重新打jar包
'打包之前先清理一下环境'
[root@Docker RuoYi-Vue]# ls ruoyi-admin/target/
classes maven-archiver ruoyi-admin.jar
generated-sources maven-status ruoyi-admin.jar.original
'现在是有jar包的'
[root@Docker RuoYi-Vue]# cat ./bin/clean.bat
@echo off
xxxx.......
cd ..
call mvn clean
👆这个
[root@Docker RuoYi-Vue]# mvn clean
[root@Docker RuoYi-Vue]# ls ruoyi-admin/target/
ls: cannot access 'ruoyi-admin/target/': No such file or directory
[root@Docker RuoYi-Vue]# mvn clean package -Dmaven.test.skip=true
# 重新打包
[INFO] ruoyi .................. SUCCESS
[INFO] ruoyi-common ........... SUCCESS
[INFO] ruoyi-system ........... SUCCESS
[INFO] ruoyi-framework ........ SUCCESS
[INFO] ruoyi-quartz ........... SUCCESS
[INFO] ruoyi-generator ........ SUCCESS
[INFO] ruoyi-admin ............ SUCCESS
[INFO] -----------------------------
[INFO] BUILD SUCCESS
[root@Docker RuoYi-Vue]# cp ruoyi-admin/target/ruoyi-admin.jar /root
# 把它移动到/root目录下!

jdk部署#

版本发布时间状态说明
JDK 82014-03LTS(长期支持)老但仍在用
JDK 112018-09LTS✅️ 企业主流之一
JDK 172021-09LTS✅️ 当前最广泛使用的 LTS
JDK 212023-09LTS✅ 最新 LTS(2023年9月发布)
JDK 222024-03GA(正式版)最新正式版
JDK 232024-09GA(2024-09-17 刚发布)当前最新正式版
  • 标签说明:21/17/11 为 Java LTS 版本
  • jre 表示仅运行时(无编译器),jdk 含编译器与调试工具

  • 在书写Dockerfile之前, 先手动跑容器把它部署起来
    • 生产运行 JAR 包(RHEL 9
      • eclipse-temurin:21-jre-ubi9-minimal
      • 本来想用ubuntu的,但是没有找到❌️
      • jre —> 只有运行环境(无编译器)
      • ubi9 —> RHEL 9
      • minimal —> 最小化安装
    • 轻量运行 JAR 包(Alpine)
      • eclipse-temurin:21-jre-alpine-3.22

OpenJDK 全指南:替代方案、实操步骤与最佳实践-阿里云开发者社区

  • 参考👆的文档
Terminal window
# 关于基础镜像的选择
刚开始想着用 openjdk,但是官方已经弃用了!
"Eclipse Temurin --> 构建OpenJDK二进制文件官方镜像"
✅️ 适用于开发、测试及生产环境
✅️ 确保良好的兼容性与稳定性
✅️ 是企业级应用开发的可靠选择
1)基础镜像拉取
[root@Docker ~]# docker pull docker.xuanyuan.run/eclipse-temurin:21-jre-ubi9-minimal
# 当然你也可以用上面的21-jre-alpine-3.22标签🏷️
[root@Docker ~]# docker images
eclipse-temurin:21-jre-ubi9-minimal
2)运行 & 拷贝jar包
[root@Docker ~]# docker run -d --name c1 eclipse-temurin:21-jre-ubi9-minimal tail -f /etc/hosts
'先给它阻塞住'
[root@Docker ~]# ls
ruoyi-admin.jar RuoYi-Vue
[root@Docker ~]# docker cp ruoyi-admin.jar c1:/home
Successfully copied 89.7MB (transferred 89.7MB) to c1:/tmp
2)试运行
[root@Docker RuoYi-Vue]# docker exec mysql-server tail -1 /etc/hosts
172.17.0.2 b96869f20e32
[root@Docker RuoYi-Vue]# docker exec redis-server tail -1 /etc/hosts
172.17.0.3 1daf7a9a3ff3
'因为我们配置文件中写的是 mysql-server 和 redis-server'
[root@b5c4c899d58a /]# cat >> /etc/hosts <<EOF
172.17.0.2 mysql-server
172.17.0.3 redis-server
EOF
[root@b5c4c899d58a /]# java -jar /home/ruoyi-admin.jar
(♥◠‿◠)ノ゙ 若依启动成功 (´ڡ`)゙
.-------. ____ __
| _ _ \ \ \ / /
| ( ' ) | \ _. / '
|(_ o _) / _( )_ .' '
| (_,_).' __ ___(_ o _)'
| |\ \ | || |(_,_)' '
| | \ `' /| `-' /
| | \ / \ /
''-' `'-' `-..-'
✅️ 后端启动成功
可以优化的点:
1.可以指定物理机路径挂载 --> 不用手动COPY至容器内了
# -v /myapp.jar:/app.jar:rw
2.使用自建网络 --> 不用手动映射域名
3.自定义启动命令 --> java -jar /app.jar
# 自动阻塞,放在前台运行

Vue & Node.js#

先搞清楚你的位置

你现在的知识:

  • HTML(结构)、CSS(样式)、JS(交互)— 浏览器直接认识的三件套
  • Nginx(反向代理、静态文件托管)
  • 运维思维(服务怎么跑、怎么部署)

现在突然冒出来 Vue 和 Node.js,感觉凭空多了两个”层”

下面从根源讲起


Vue#

为什么需要 Vue?原生 JS 哪里不够用?

以前的写法(你熟悉的)

index.html
<div id="counter">
<span id="count">0</span>
<button onclick="add()">+1</button>
</div>
<script>
let n = 0;
function add() {
n++;
document.getElementById('count').innerText = n; // 手动更新 DOM
}
</script>

一个计数器,十几行代码,还行。


页面复杂以后…

假设你要做一个后台管理面板

  • 侧边栏菜单(展开/折叠)
  • 表格(排序、分页、搜索)
  • 弹窗(新增/编辑用户)
  • 登录状态(未登录跳转)
  • 权限控制(管理员看到不同菜单)
  • 多个页面切换(列表页 → 详情页 → 编辑页)

用原生 JS 写:

Terminal window
手动操作 DOM:document.getElementById、innerHTML、appendChild...
数据和页面不同步:数据变了,你得记住去更新页面上"所有相关"的地方
====================
代码组织混乱:一个 script.js 几千行,变量满天飞

核心矛盾:数据变了,你得手动把页面上所有用到这个数据的地方全部更新一遍

漏一个地方就出 bug


==Vue 解决的就是这个问题==

Vue 是一个前端框架

它的核心思想:

你只管改数据,页面自动跟着变

上面的计数器用 Vue 写:

<template>
<div>
<span>{{ count }}</span> <!-- 自动绑定,count 变了这里自动更新 -->
<button @click="add">+1</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
function add() { count.value++; } // 只改数据,不管 DOM
</script>

你发现区别了吗?

没有 document.getElementById,没有 innerText

你只改了 count.value,页面上所有用到 count 的地方自动刷新


Vue 还解决了第二个问题:==组件化==

一个页面拆成独立组件,每个组件管自己那一摊:

Terminal window
App.vue(整体布局)
├── Sidebar.vue 侧边栏,里面有自己的展开/折叠逻辑
├── Header.vue 顶栏,里面有用户头像、退出按钮
├── UserTable.vue 表格,里面有排序、分页逻辑
└── UserRow.vue 每一行
└── Modal.vue 弹窗,通用组件到处复用
  • 每个 .vue 文件包含自己的 HTML + CSS + JS,互不干扰

  • 就跟 Nginx 配多个 server 块一样,各管各的


==小结==:Vue 解决什么?

原生 JS 的痛点Vue 的方案
手动操作 DOM,容易漏响应式: 改数据,页面自动更新
代码堆在一个文件,难以维护组件化: 拆成小块,每个管自己
页面切换要自己写路由逻辑Vue Router: 自动管理页面切换
数据在页面间传递混乱状态管理(Pinia): 集中管理共享数据

Vue = 让你用更少、更清晰的代码,写出更复杂的页面


Node.js#

那 Node.js 是干嘛的?

你可能会问:“我用原生 JS 从来没有 Node.js,为什么用 Vue 就多出来一个 Node.js

答案很简单: ==浏览器只认识三样东西:HTML、CSS、JS==

你写的 .vue 文件长这样:

<template>...</template> ← 这既不是 HTML,也不是 JS
<script setup>...</script> ← 这段 JS 里有 import 语句,浏览器不认识
<style scoped>...</style> ← 这段 CSS 浏览器也不直接认识

浏览器拿到 .vue 文件 = 完全看不懂


所以需要一个”翻译工”

Node.js 就是这个翻译工

Node.js 本质是什么?

Node.js = 把 Chrome 浏览器的 JS 引擎(V8)单独拆出来,让你在服务器/本机也能跑 JS

浏览器里的 JS:能操作网页,但不能读写你电脑上的文件 Node.js 里的 JS:==能读写文件==、==启动网络服务==、==执行命令行==——因为它跑在操作系统上,不跑在浏览器里


在前端项目里,Node.js 是编译工具,不是服务

Terminal window
你写的源码 Node.js(运行 Vite) 浏览器看到的
────────── ─"编译"→ ────────────────── ─"返回"→ ────────
App.vue 解析 .vue 拆成三部分 index.html
components/Header.vue 打包所有 JS 一个 app.js app.js( JS)
style.css PostCSS 处理 加浏览器前缀 style.css(处理好的)
article.md markdown-it 转成 HTML 内嵌在 JS 里的 HTML

Node.js 在这里的角色 = 编译流水线

==它把源码加工成浏览器能吃的静态文件,仅此而已==


为什么开发时要一直开着?

Terminal window
npm run dev Node.js 启动 Vite 监听文件变化
你改了 App.vue Vite 检测到 重新编译 通过 WebSocket 推给浏览器
浏览器只更新变动的组件,不刷新页面(HMR 热更新)

开着它是为了边改边看


开发 vs 生产#

Terminal window
"开发时:"
npm run dev Node.js + Vite 开发服务器(localhost:5175)
- 编译 .vue 文件
- 热更新
- 只用于开发,性能差,绝不能对外暴露
==============================================
"生产时:"
npm run build Node.js 跑一次编译 生成 dist/ 静态文件夹
之后 Node.js 退出,不再需要 ✅️
Nginx 托管 dist/ 返回 HTML/CSS/JS 用户浏览器
+ 反向代理 /api/* 到后端(Java/Go)

部署后,没有 Node.js 进程,只有 Nginx + 一堆静态文件

这一点经常被误解

很多运维看到项目里有 Node.js,以为部署时也要装 Node.js 跑服务——不需要❌️

npm run build 之后那堆 dist/ 文件,跟你以前丢到 Nginx html/ 目录下的 index.htmlstyle.cssscript.js 本质上是一回事


完整的请求链路#

Terminal window
用户浏览器
├── 请求 index.html Nginx dist/index.html
<!DOCTYPE html>
<script src="/assets/app.js"> 浏览器再请求这个 JS
├── 请求 /assets/app.js Nginx dist/assets/app.js
里面就是 Vue 编译后的纯 JS
浏览器拿到后执行,页面就出来了
└── 请求 /api/users Nginx proxy_pass 后端(Go/Python/Java)
返回 JSON 数据

跟以前纯 HTML 时代的区别只有一个:以前 app.js 你手写的;现在 app.js 是 Vue 源码编译出来的


打个比方总结#

Terminal window
Vue 源码 = 面粉(浏览器吃不了)
Node.js = 烤箱(只参与加工)
Vite = 烤箱的控制面板(调温度、定时)
npm run dev = 开着烤箱门,边烤边尝(热更新)
npm run build = 一次性烤好(生成 dist/)
dist/ = 烤好的面包(纯 HTML/CSS/JS)
Nginx = 服务员端给顾客(浏览器)
后端 API = 厨房(提供数据)
"开发时":烤箱开着,你边改配方边尝味道
"上线后":面包早就烤好了,服务员直接端,烤箱早就关了

node.js容器#

Terminal window
[root@Docker ~]# docker pull docker.xuanyuan.run/library/node:24.10.0;
# 改了一个标签
[root@Docker ~]# docker images
node:24.10.0 06e54ecf113a 1.63GB
[root@Docker ~]# docker run -d -it -p 80:80 --name c2 node:24.10.0
'映射了一下端口,方便我们后面浏览器访问它'
[root@Docker ~]# docker ps -l
c56a40e88634 node:24.10.0 "docker-entrypoint.s…" 4 seconds ago Up 3 seconds
[root@Docker ~]# docker cp ~/RuoYi-Vue/ruoyi-ui c2:/
Successfully copied 198MB (transferred 226MB) to c2:/
'把前端页面都拷贝过去'
[root@Docker ~]# docker exec -it c2 /bin/bash
root@c56a40e88634:/# ls /ruoyi-ui/
README.md babel.config.js bin build node_modules package-lock.json package.json public src vue.config.js
'我们之前nmp install下载了一些依赖'
# node_modules/目录 和 package-lock.json文件
root@c56a40e88634:/# rm -rf /ruoyi-ui/node_modules/ /ruoyi-ui/package-lock.json
'把它们都删除了 <-- 也可以不删除'
root@c56a40e88634:/# cd /ruoyi-ui/
✅️ 更改一下后端接口(java)
root@c56a40e88634:/ruoyi-ui# grep -r localhost:8080 /ruoyi-ui
....xxxx
/ruoyi-ui/vue.config.js:const baseUrl = 'http://localhost:8080' // 后端接口
# 用grep过滤出更改后端接口的配置文件
root@c56a40e88634:/ruoyi-ui# sed -i "s#localhost:8080#c1:8080#g" /ruoyi-ui/vue.config.js
'我们后端容器(运行jar包)为c1容器'
root@c56a40e88634:/ruoyi-ui# echo 172.17.0.2 c1 >> /etc/hosts
root@c56a40e88634:/ruoyi-ui# tail -1 /etc/hosts
172.17.0.2 c1
root@c56a40e88634:/ruoyi-ui# npm install --registry=https://registry.npmmirror.com
# 下载nmp依赖
root@c56a40e88634:/ruoyi-ui# npm run dev
# 启动前端项目
App running at:
- Local: http://localhost:80/
✅️ 最后也是能够成功访问到了
'有验证码,没有接口报错'

image-20260508144707549
image-20260508144707549

nginx容器#

image-20260508150408908
image-20260508150408908

  • 我们这次直接模拟生产环境

npm run build → Node.js 跑一次编译 → 生成 dist/ 静态文件夹

Terminal window
1)打包build
[root@Docker ~]# cd RuoYi-Vue/ruoyi-ui/
'容器外的代码'
# 里面的接口地址,数据啥的都没有改变
[root@Docker ruoyi-ui]# ls
babel.config.js bin build node_modules package.json ...
[root@Docker ruoyi-ui]# npm run build:prod
# 打包正式环境
[root@Docker ruoyi-ui]# ls ./dist/
favicon.ico html index.html index.html.gz robots.txt static styles
'所有的静态文件都在这里面了'
# 在前端构建过程中,会自动生成 gzip 压缩版本的静态资源文件
✅️ 提高网络传输效率
2)停止node.js & 运行nginx容器
[root@Docker ruoyi-ui]# docker stop c2
[root@Docker ruoyi-ui]# docker run -d --name c3 -p 80:80 nginx:latest
[root@Docker ruoyi-ui]# docker exec -it c3 /bin/bash
# 修改/etc/nginx/conf.d子配置文件即可
root@dca1989d9174:~# cd /etc/nginx/conf.d/
root@dca1989d9174:/etc/nginx/conf.d# > default.conf
root@dca1989d9174:/etc/nginx/conf.d# cat default.conf
'提前写到宿主机中, 然后COPY过来'
[root@Docker ~]# docker cp default.conf c3:/etc/nginx/conf.d/
Successfully copied ... to c3:/etc/nginx/conf.d/
server {
listen 80;
server_name localhost;
charset utf-8;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
index index.html;
}
location /prod-api/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_pass http://localhost:8080/;
proxy_pass http://c1:8080/;
# c1运行着后端程序
}
# springdoc proxy
location ~ ^/v3/api-docs/(.*) {
# proxy_pass http://localhost:8080/v3/api-docs/$1;
proxy_pass http://c1:8080/v3/api-docs/$1;
# 同上
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Terminal window
root@dca1989d9174:/etc/nginx/conf.d# echo 172.17.0.2 c1 >> /etc/hosts
root@dca1989d9174:/etc/nginx/conf.d# nginx -t
nginx: the configuration file ... is ok
nginx: configuration file ... is successful
root@dca1989d9174:/etc/nginx/conf.d# ls /usr/share/nginx/html/
50x.html index.html
'还没拷贝前端代码文件'
[root@Docker ~]# ls ~/RuoYi-Vue/ruoyi-ui/dist/
favicon.ico html index.html index.html.gz robots.txt static styles
[root@Docker ~]# docker cp ~/RuoYi-Vue/ruoyi-ui/dist/ c3:/usr/share/nginx/html/
Successfully ... to c3:/usr/share/nginx/html/
root@dca1989d9174:/etc/nginx/conf.d# ls /usr/share/nginx/html/
50x.html dist index.html
'多个dist目录'
# 把目录里面的东西全部拿出来
root@dca1989d9174:/etc/nginx/conf.d# cp -r /usr/share/nginx/html/dist/* /usr/share/nginx/html/
root@dca1989d9174:/etc/nginx/conf.d# rm -rf /usr/share/nginx/html/dist/
root@dca1989d9174:/etc/nginx/conf.d# cd /usr/share/nginx/html/
root@dca1989d9174:/usr/share/nginx/html# ls -lh
total 40K
-rw-r--r-- 1 root root 497 Apr 7 11:37 50x.html
-rw-r--r-- 1 root root 5.6K May 8 07:48 favicon.ico
drwxr-xr-x 2 root root 39 May 8 07:48 html
-rw-r--r-- 1 root root 13K May 8 07:48 index.html
-rw-r--r-- 1 root root 4.2K May 8 07:48 index.html.gz
-rw-r--r-- 1 root root 26 May 8 07:48 robots.txt
drwxr-xr-x 6 root root 51 May 8 07:48 static
drwxr-xr-x 3 root root 25 May 8 07:48 styles
'权限有问题 ❌️'
root@dca1989d9174:/usr/share/nginx/html# chown -R nginx:nginx ../html/
# 都改为nginx用户
[root@Docker ~]# docker ps
nginx:latest Up [::]:80->80/tcp c3
eclipse-temurin:21-jre-ubi9-minimal Up c1
redis:7.2.8 Up [::]:6379->6379/tcp redis-server
mysql:8.0.36 Up [::]:3306->3306/tcp mysql-server
'node.js容器并没有运行'
✅️ 通过nginx依旧可以访问

image-20260508161811044
image-20260508161811044

Dockerfile书写#

Terminal window
[root@Docker ~]# mkdir RuoYi-compose && cd RuoYi-compose
[root@Docker RuoYi-compose]# mkdir {conf,dockerfile,data}
[root@Docker RuoYi-compose]# tree ./
./
├── conf
├── data
└── dockerfile

数据库#

Terminal window
1)准备sql文件
[root@Docker RuoYi-compose]# cd /root/RuoYi-Vue/sql/
[root@Docker sql]# ls
quartz.sql ry_20260417.sql
[root@Docker sql]# cp ./* /root/RuoYi-compose/data/
[root@Docker sql]# cd -
/root/RuoYi-compose
[root@Docker RuoYi-compose]# ls data/
quartz.sql ry_20260417.sql
2)以防字符乱码my.cnf
[root@Docker RuoYi-compose]# docker exec -it mysql-server /bin/bash
bash-4.4# cat /etc/my.cnf
[mysqld]
skip-host-cache
skip-name-resolve
datadir=/var/lib/mysql
socket=/var/run/mysqld/mysqld.sock
secure-file-priv=/var/lib/mysql-files
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[mysql]
default-character-set = utf8mb4
[client]
socket=/var/run/mysqld/mysqld.sock
default-character-set = utf8mb4
!includedir /etc/mysql/conf.d/
bash-4.4# exit
exit
[root@Docker RuoYi-compose]# docker cp mysql-server:/etc/my.cnf ./conf/
Successfully copied ... to /root/RuoYi-compose/conf/
[root@Docker RuoYi-compose]# ls ./conf/
my.cnf
3)docker-entrypoint-initdb.d目录
✅️ MySQL Docker 官方镜像的一个特殊目录
1.存放数据库初始化时要执行的脚本或sql语句
2.只有在首次初始化数据库时才会执行
'把sql语句放在这个目录中'
  • 因为本身MySQL镜像里面的环境变量、启动命令…就够用了
    • 这里的Dockerfile只是为了解决乱码sql脚本导入
      • vim ./dockerfile/mysql-1.0
      • 太多COPY —> 后期考虑写shell脚本
FROM mysql:8.0.36
LABEL maintainer="jiuzhao"
EXPOSE 3306
COPY ./conf/my.cnf /etc/my.cnf
COPY ./data/quartz.sql /docker-entrypoint-initdb.d/
COPY ./data/ry_20260417.sql /docker-entrypoint-initdb.d/
Terminal window
4)镜像构建
[root@Docker RuoYi-compose]# docker build -f ./dockerfile/mysql-1.0 -t ruoyi-mysql:1.0 .
[+] Building 0.6s (8/8) FINISHED
'还是很顺利的'
5)清理环境 & 创建网络
[root@Docker RuoYi-compose]# docker rm -f $(docker ps -aq)
[root@Docker RuoYi-compose]# docker network prune -f
[root@Docker RuoYi-compose]# docker volume prune -fa
[root@Docker RuoYi-compose]# docker network create -d bridge --subnet 172.20.0.0/16 --gateway 172.20.0.1 mynet
[root@Docker RuoYi-compose]# docker network ls | grep mynet
691fc0a12189 mynet bridge local
6)测试验证
[root@Docker RuoYi-compose]# docker container run \
-e MYSQL_ALLOW_EMPTY_PASSWORD="yes" \
-d \
--net mynet \
--name mysql-server \
-e MYSQL_DATABASE="ry-vue" \
-e MYSQL_USER="jiu" \
-e MYSQL_PASSWORD="oldboy123.com" \
ruoyi-mysql:1.0 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci \
--default-authentication-plugin=mysql_native_password
# 没有暴漏端口 & 用的自己的镜像
[root@Docker RuoYi-compose]# docker exec -it mysql-server /bin/bash
bash-4.4# mysql
Welcome to the MySQL monitor.
mysql> use ry-vue;
Database changed
'数据库中有对应的数据!'
mysql> show tables;
+--------------------------+
| Tables_in_ry-vue |
+--------------------------+
| QRTZ_BLOB_TRIGGERS |
| QRTZ_CALENDARS |
| QRTZ_CRON_TRIGGERS |
mysql> SHOW VARIABLES LIKE 'character%';
mysql> SET NAMES utf8mb4;
+--------------------------+--------
| Variable_name | Value
+--------------------------+--------
| character_set_client | utf8mb4 ✅️
| character_set_connection | utf8mb4 ✅️
| character_set_results | utf8mb4 ✅️
'字符集也是对的'
7)Redis数据库
# 不需要书写对应的Dockerfile,直接跑起容器就可以了
[root@Docker RuoYi-compose]# docker container run -d \
--name redis-server \
--net mynet \
redis:7.2.8
# 隐藏端口
[root@Docker RuoYi-compose]# docker ps
redis:7.2.8 "docker-entrypoint.s…" UP redis-server

后端#

Terminal window
"ruoyi-server"
1)清理与检查
[root@Docker RuoYi-compose]# cd /root/RuoYi-Vue/
[root@Docker RuoYi-Vue]# mvn clean
# 清理target目录
# 删除编译后的类文件、打包的 JAR/WAR 文件
⚠️ mave打包为jar包之前,需要先检查相应配置
# mysql和redis的地址指向
[root@Docker RuoYi-Vue]# grep "mysql-server" /root/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml
url: jdbc:mysql://mysql-server:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
[root@Docker RuoYi-Vue]# grep "redis-server" /root/RuoYi-Vue/ruoyi-admin/src/main/resources/application.yml
host: redis-server
2)打为jar包
[root@Docker RuoYi-Vue]# ls ruoyi-admin/target/
ls: cannot access 'ruoyi-admin/target/': No such file or directory
[root@Docker RuoYi-Vue]# mvn clean package -Dmaven.test.skip=true
[root@Docker RuoYi-Vue]# ls ruoyi-admin/target/ruoyi-admin.jar
ruoyi-admin/target/ruoyi-admin.jar
[root@Docker RuoYi-Vue]# mv ruoyi-admin/target/ruoyi-admin.jar /root/RuoYi-compose/data/
[root@Docker RuoYi-Vue]# cd -
/root/RuoYi-compose
[root@Docker RuoYi-compose]# ls ./data/ruoyi-admin.jar
./data/ruoyi-admin.jar
3)编写Dockerfile文件
[root@Docker RuoYi-compose]# vim ./dockerfile/jre-1.0
FROM eclipse-temurin:21-jre-ubi9-minimal
LABEL maintainer="jiuzhao"
EXPOSE 8080
COPY ./data/ruoyi-admin.jar /
CMD ["java", "-jar", "/ruoyi-admin.jar"]
Terminal window
4)构建镜像
[root@Docker RuoYi-compose]# docker build -f ./dockerfile/jre-1.0 -t ruoyi-backend:1.0 .
[+] Building 4.7s (7/7) FINISHED
[root@Docker RuoYi-compose]# docker images
ruoyi-backend:1.0 2eead08a0bbf 603MB
5)运行测试
[root@Docker RuoYi-compose]# docker run -d --net mynet --name ruoyi-server ruoyi-backend:1.0
[root@Docker RuoYi-compose]# docker ps -l
17133f983cb0 ruoyi-backend:1.0 "/__cacert_entrypoin…" 4 seconds ago Up 3 seconds 8080/tcp ruoyi-server
[root@Docker RuoYi-compose]# docker logs ruoyi-server
(♥◠‿◠)ノ゙ 若依启动成功 (´ڡ`)゙
.-------. ____ __
| _ _ \ \ \ / /
| ( ' ) | \ _. / '
|(_ o _) / _( )_ .' '
| (_,_).' __ ___(_ o _)'
| |\ \ | || |(_,_)' '
| | \ `' /| `-' /
| | \ / \ /
''-' `'-' `-..-'

前端#

Terminal window
"ruoyi-ui"
1)拷贝dist目录下的资源
[root@Docker RuoYi-compose]# cp -r /root/RuoYi-Vue/ruoyi-ui/dist/ ./data/
[root@Docker RuoYi-compose]# ls -d ./data/dist/
./data/dist/
# 把整个dist拷贝过来了
2)Nginx配置文件
[root@Docker RuoYi-compose]# vim ./conf/default.conf
server {
listen 80;
server_name localhost;
charset utf-8;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
index index.html;
}
location /prod-api/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://ruoyi-server:8080/;
# 后端容器的名字
}
# springdoc proxy
location ~ ^/v3/api-docs/(.*) {
proxy_pass http://ruoyi-server:8080/v3/api-docs/$1;
# 同上
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
3)编写Dockerfilefile
[root@Docker RuoYi-compose]# vim ./dockerfile/nginx-1.0
FROM nginx:latest
LABEL maintainer="jiuzhao"
EXPOSE 80
COPY ./conf/default.conf /etc/nginx/conf.d/
COPY ./data/dist /usr/share/nginx/html
RUN chown -R nginx:nginx /usr/share/nginx/html
Terminal window
4)构建镜像
[root@Docker RuoYi-compose]# docker build -f ./dockerfile/nginx-1.0 -t ruoyi-frontend:1.0 .
[+] Building 1.5s (9/9) FINISHED
[root@Docker RuoYi-compose]# docker images
ruoyi-frontend:1.0 becfb6f20726 257MB
5)测试运行
[root@Docker RuoYi-compose]# docker run -d --name ruoyi-ui --net mynet -p 80:80 ruoyi-frontend:1.0
'这个得映射端口'
[root@Docker RuoYi-compose]# docker logs ruoyi-ui
2026/05/08 11:30:16 [notice] 1#1: nginx/1.29.8
2026/05/08 11:30:16 [notice] 1#1: built by gcc 14.2.0 (Debian 14.2.0-19)
2026/05/08 11:30:16 [notice] 1#1: OS: Linux 6.12.0-124.8.1.el10_1.x86_64
2026/05/08 11:30:16 [notice] 1#1: start worker processes
2026/05/08 11:30:16 [notice] 1#1: start worker process 29
2026/05/08 11:30:16 [notice] 1#1: start worker process 30
'也是启动起来了!'
# 浏览器访问
http://10.0.0.99

image-20260508211751379
image-20260508211751379

Docker compose编排#

services:
mysql-server:
image: ruoyi-mysql:1.0
container_name: mysql-server
healthcheck:
test: ["CMD", "mysqladmin", "ping"]
interval: 5s
timeout: 5s
retries: 3
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
MYSQL_DATABASE: "ry-vue"
MYSQL_USER: "jiu"
MYSQL_PASSWORD: "oldboy123.com"
command: ["--character-set-server=utf8", "--collation-server=utf8_bin", "--default-authentication-plugin=mysql_native_password"]
volumes:
- db:/var/lib/mysql:rw
networks:
- ruoyi
# 指定容器的重启策略
restart: unless-stopped
redis-server:
image: redis:7.2.8
restart: unless-stopped
container_name: redis-server
healthcheck:
test: ["CMD", "redis-benchmark", "-h","127.0.0.1","-p", "6379","-n", "1", "-c","2"]
interval: 5s
timeout: 5s
retries: 3
networks:
- ruoyi
ruoyi-server:
image: ruoyi-backend:1.0
restart: unless-stopped
container_name: ruoyi-server
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/"]
interval: 15s
timeout: 15s
retries: 3
# 依赖的服务要优先启动
depends_on:
mysql-server:
condition: service_healthy
# MySQL和Redis 健康检查通过才启动
redis-server:
condition: service_healthy
networks:
- ruoyi
ruoyi-ui:
image: ruoyi-frontend:1.0
restart: unless-stopped
container_name: ruoyi-ui
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 15s
timeout: 15s
retries: 3
depends_on:
ruoyi-server:
condition: service_healthy
ports:
- "80:80"
networks:
- ruoyi
networks:
ruoyi:
name: ruoyi-net
driver: bridge
ipam:
driver: default
config:
- subnet: 172.18.0.0/16
gateway: 172.18.0.1
volumes:
db:
name: mysql-volume

健康检查重要性#

💡 depends_on 默认只等容器启动,不等服务就绪,加 healthcheck 可以解决启动顺序问题

  • ==笨方法== —> 重启策略 —> unless-stopped
Terminal window
[root@Docker RuoYi-compose]# vim docker-compose.yml
# 内容如上
1)清理环境
[root@Docker RuoYi-compose]# docker rm -f $(docker ps -aq)
[root@Docker RuoYi-compose]# docker network prune -f
[root@Docker RuoYi-compose]# docker volume prune -fa
2)一键启动
[root@Docker RuoYi-compose]# docker compose up -d
[+] up 6/6
Network ruoyi-net Created
Volume mysql-volume Created
Container redis-server Healthy
Container mysql-server Healthy
Container ruoyi-server Healthy
Container ruoyi-ui Started
[root@Docker RuoYi-compose]# docker compose ls
ruoyi-compose running(4) /root/RuoYi-compose/docker-compose.yml
[root@Docker RuoYi-compose]# docker compose ps
ruoyi-mysql:1.0 "docker-entrypoint.s…" mysql-server About a minute ago Up About a minute 3306/tcp, 33060/tcp
redis:7.2.8 "docker-entrypoint.s…" redis-server About a minute ago Up About a minute 6379/tcp
ruoyi-backend:1.0 "/__cacert_entrypoin…" ruoyi-server About a minute ago Up 50 second 8080/tcp
ruoyi-frontend:1.0 "/docker-entrypoint.…" ruoyi-ui About a minute ago Up About a minute [::]:80->80/tcp
  • 最后也是可以成功访问到 ✅️

image-20260511104200309
image-20260511104200309

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

传统业务容器化
https://www.kpyun.fun/posts/docker/docker07/
作者
久棹
发布于
2026-03-03
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
久棹
只要胆子大,天天寒暑假!
公告
欢迎来到久棹的技术小站!本站专注 Linux 运维学习笔记分享,如有问题欢迎交流探讨 🎉
分类
标签
站点统计
文章
98
分类
11
标签
203
总字数
244,453
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录