背景
搭建一个网站需要哪些准备哪些内容?
- 准备一台云服务器
- 前端、后端、数据库
- docker,用来更快捷的部署
- 域名、ssl证书,便于安全的访问
购买服务器
- 选择地区,选一个离自己最近的
- 选择产品规格,我选了2核2G的,个人搭着玩,这个配置暂时够用了
- 选择操作系统,目前默认的Alibaba Cloud Linux 3.2,选这个就行,千万别选一些很老的操作系统,此处点名centos7,在centos7上运行不了node.js18。
- 分配公网IPv4地址,勾选,阿里云会分配一个公网ip,就能通过这个ip访问你的服务了
- 支付后,就会获得一个云服务器实例,
登录服务器
- 绑定ssh密钥。Alibaba Cloud Linux 3.2此操作系统,不允许使用密码登录,只能使用ssh密钥进行登录。参考文档,大部分问题都能在产品文档中找到,不行,就问客服。这一步会下载一个包含私钥的文件
- 菜单栏选择实例,点击操作列中的远程连接
- 选择使用ssh登录,导入在第一步得到的私钥
- 登录成功,进入服务器页面!
- 云服务器有安全组的概念,就是对外部开放哪些端口,后面部署应用时会用到
应用间的交互约定
- 宿主机443端口运行着nginx,由nginx根据路径分发到对应的应用中
- blog-app-fe博客展示前端,使用/blog前缀,运行在服务器3001端口
- blog-app-backend博客后台,使用/blogApi前缀,运行在服务器3003端口
部署前端应用
打包配置
assets需要配置路径/blog的路径前缀
export default defineConfig(({ mode }) => {
const isDevelopment = mode === 'development';
return {
// 其他省略
// 开发环境进行转发
server: {
proxy: {
'/blogApi': {
target: 'http://localhost:4000',
changeOrigin: true,
rewrite: path => path.replace(/^\/blogApi/, ''), // 可选: 修改路径
},
},
},
build: {
// 加上前缀
assetsDir: 'blog/assets',
rollupOptions: {
output: {
manualChunks(id) {
// 例如,将 'lodash' 和 'axios' 拆分为独立的包
if (id.includes('node_modules/element-plus')) {
return 'element-plus'; // 生成 lodash.js
}
if (id.includes('node_modules/pdf-vue3')) {
return 'pdf-vue3'; // 生成 axios.js
}
},
},
},
},
};
});
打包后的index.html,这样请求这些资源就会加上blog前缀,可以通过nginx来转发到正确的路径
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>影月的个人博客</title>
<script type="module" crossorigin src="/blog/assets/index-Kd-06mGt.js"></script>
<link rel="modulepreload" crossorigin href="/blog/assets/element-plus-B7Zl2J6f.js">
<link rel="stylesheet" crossorigin href="/blog/assets/element-plus-DzIbptqk.css">
<link rel="stylesheet" crossorigin href="/blog/assets/index-CnUOfbc3.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
构建镜像,完成部署
编写dockerfile用来构建镜像
- 使用指定版本的工具进行打包,避免出现版本不兼容的情况
- 把打包产物放在nginx服务器上
- 启动nginx服务器
# 使用官方 Node.js 镜像作为构建基础镜像
FROM node:18.17.1 AS builder
# 安装 pnpm
RUN npm install -g pnpm@8.7.0
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 pnpm-lock.yaml(如果有的话)到工作目录
COPY package*.json pnpm-lock.yaml* ./
# 安装应用依赖
RUN pnpm install
# 复制应用源代码到工作目录
COPY . .
# 构建应用
RUN pnpm run build
# 使用 Nginx 镜像作为生产环境镜像
FROM nginx:alpine
# 复制自定义 Nginx 配置文件
COPY nginx.conf /etc/nginx/nginx.conf
# 将构建产物从 /app/dist 复制到 Nginx 的默认服务目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 暴露端口 3001
EXPOSE 3001
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]
把镜像上传到dockerhub
# 构建镜像并上传
docker buildx build --platform linux/amd64 -t mhhongfe/blog-app-fe:20241121 --push .
# 在服务器上拉取镜像
docker pull mhhongfe/blog-app-fe:20241121
# 程序在容器内的3001端口运行,容器在宿主机的3001端口运行
docker run -p 3000:3001 mhhongfe/blog-app-fe:1.0
# 如果之前有相同端口的容器在运行,需要停止之前的容器
docker stop container_id
容器内的nginx配置
因为使用的是history api的路由,因此需要配置try files避免找不到资源
# 全局上下文
worker_processes 1; # 设置工作进程数
# 事件块
events {
worker_connections 1024; # 设置每个工作进程的最大连接数
}
# HTTP 块
http {
include mime.types;
default_type application/octet-stream;
# 服务器块
server {
listen 3001;
server_name localhost;
# 根目录的静态文件
location / {
root /usr/share/nginx/html;
try_files $uri /index.html; # 确保单页面应用的路由正常工作
}
}
}
部署后端应用
集成数据库
1. 本地安装数据库
# 安装
brew install mysql
# 启动数据库
brew services start mysql
# 登陆数据库
mysql -u root -p
2. 建表
目前博客仅有三张表,用户表、分类表、文章表
-- 创建用户表
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
-- 用户ID,主键,自增
user_name VARCHAR(50) NOT NULL UNIQUE,
-- 用户名,非空且唯一
user_password VARCHAR(255) NOT NULL,
-- 密码,非空
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
desc articles;
-- 创建分类表
CREATE TABLE category (
id INT AUTO_INCREMENT PRIMARY KEY,
category_name VARCHAR(50) NOT NULL UNIQUE,
category_desc VARCHAR(100) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建文章表
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
-- 唯一标识,每个博客文章的自增 ID
title VARCHAR(255) NOT NULL,
-- 文章标题,最大长度为 255 字符
content MEDIUMTEXT NOT NULL,
-- 文章内容,存储中大型文本
article_desc VARCHAR(100),
author VARCHAR(100),
-- 作者名称,最大长度为 100 字符
category_id INT,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 文章创建时间,默认值为当前时间
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- 文章更新时间
view_count INT DEFAULT 0 -- 浏览次数,初始为 0
);
3. express连接数据库
express中使用mysql2连接数据库
pnpm install mysql2
添加数据库配置
const mysql = require("mysql2/promise");
// 需要把这里换成真实的服务器信息
// 设置MySQL连接池
const pool = mysql.createPool({
host: "searverIp",
port: 3306,
user: "root",
password: "xxxxxx",
database: "xxxxx",
waitForConnections: true,
connectionLimit: 10, // 连接池中允许的最大连接数
queueLimit: 0, // 队列中允许的最大请求数,0表示无限制
});
module.exports = pool;
在接口中访问数据库
var express = require("express");
const pool = require("../config/dbConfig");
var router = express.Router();
router.get("/list", async (req, res) => {
let connection;
try {
// 从连接池中获取一个连接
connection = await pool.getConnection();
const sqlQuery = `SELECT * FROM category`;
const [rows, fields] = await connection.execute(sqlQuery);
res.status(200).json({
code: 200,
msg: "查询成功",
data: transformArray(rows),
});
} catch (err) {
// 处理错误
console.error("Error executing query:", err);
res.status(500).json({ error: "Internal Server Error" });
} finally {
// 确保连接被释放回连接池(如果它仍然存在)
if (connection) {
connection.release();
}
}
});
服务器上连接数据库
在服务器上安装mysql,参考在Linux实例中安装MySQL数据库,登陆、建表过程如上节,不再赘述。
express应用部署在容器中,mysql部署在宿主机上,可能会导致连接不上,需要开放mysql被连接的权限
-
mysql默认只能被自身ip连接,需要开放被外部ip连接,在/etc/my.cnf文件上修改
[mysqld] bind-address = 0.0.0.0 -
授予特定用户从指定ip访问数据库的权限
# 如果没有用户,需要先创建用户 CREATE USER 'app_user'@'your_container_ip' IDENTIFIED BY '@Hmh6861860@'; # 授予这个用户对数据任意操作的权限,登陆mysql后操作,在>mysql>下进行 GRANT ALL PRIVILEGES ON your_database.* TO 'app_user'@'your_container_ip'; FLUSH PRIVILEGES; -
开放安全组 允许容器ip访问3306端口
编写dockerfile
# 使用官方 Node.js 镜像作为构建基础镜像
FROM node:18.17.1 AS builder
# 安装 pnpm
RUN npm install -g pnpm@8.7.0
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 pnpm-lock.yaml(如果有的话)到工作目录
COPY package*.json pnpm-lock.yaml* ./
# 安装应用依赖
RUN pnpm install
# 复制应用源代码到工作目录
COPY . .
# 暴露端口 3003
EXPOSE 3003
# 启动应用程序
CMD ["npm", "run", "start"]
使用nginx转发应用
宿主机上的nginx配置
server {
listen 443 ssl;
location /favicon.ico {
proxy_pass http://localhost:3001/favicon.ico; # 转发到宿主机的 3001 端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /blog/ {
proxy_pass http://localhost:3001/blog/; # 转发到宿主机的 3001 端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /blogApi/ {
# 去除路径前缀 /blogApi
rewrite ^/blogApi(/.*)$ $1 break;
# 将请求转发到本地的 3003 端口
proxy_pass http://localhost:3003;
# 保留请求头信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
使用https访问网站
使用https的前提是拥有域名和ssl证书
- 购买域名
- 配置dns解析,关联ip和域名
- 购买ssl证书,这一步能拿到ssl证书密钥文件
- 上传密钥到服务器,通过vscode连接服务器,可以直接把本地文件拖动到服务器上
- nginx配置ssl信息
server {
listen 443 ssl;
server_name www.xxx.com; # 这里使用你的域名
# SSL 证书和私钥路径
ssl_certificate /etc/ssl/certs/personal-site.pem;
ssl_certificate_key /etc/ssl/certs/personal-site.key;
# 启用 SSL 协议
ssl_protocols TLSv1.2 TLSv1.3;
}