自建博客网站+自动化部署+内网穿透

发布于:2024-04-26 ⋅ 阅读:(18) ⋅ 点赞:(0)

自建博客网站

访问地址:

image-20240319131032048

最开始使用 React 框架搭建了一套 SPA 网页,部署在 nginx 上,打包后的文件体积为 2M,放在腾讯云服务器上,下载完预计 12s 左右,体验感很差。

为了优化加载速度,优化点1: 将腾讯云的网络带宽从 1M 变为了 2M;优化点2: 使用 nextjs 框架优化加载速度,对网页资源进行分块。

使用 nextjs 中的动态路由后,无法部署静态网页,nginx 无法使用,所以打算在服务器上跑npm run start 命令启动项目。令人意外的是 1 核 1 GB 内存的服务器启动该命令后,直接产生 OOM 问题,内存溢出。云服务器内存溢出后将无法从常规方式进入控制台,远程连接会失效,需要重启服务器。

有一种解决办法是提高服务器配置,提高到 2 核 4 G,一年 1200。我已经动心了,只是突然看到角落里有一台自用神舟笔记本,平常玩游戏比较多。想想玩游戏肯定比不上学习,所以我动了用笔记本作为程序部署机器的想法。

这台神舟笔记本的配置还是不错的,16G 内存,i7 芯片,1060 的GPU,完胜云服务器。用笔记本做服务器的难点在如何固定外网 IP,现在固定 ip4 的地址基本不给发放。调研相关实现方式后,决定使用 frp 做内网穿刺,使用云服务器作为外网跳板。后续用户访问逻辑为:用户 -》 云服务器 -〉笔记本 -》云服务器 -〉用户

为了方便开发和部署,笔者使用 vs code 和 ssh 配置了云服务器和笔记本的远程访问,文件传输,代码编写,环境配置都方便了很多,后面用一台主开发机就能操控云服务器和神舟笔记本了。

为了进一步提高开发体验,笔者将代码更新和部署工作自动化了。将代码托管到码云上,每次 push 动作触发后,码云会向部署机上发送更新命令。部署机收到后会重新安装依赖,再启动项目。

前端技术选型

next + process + uiw/react-md-editor + react-markdown + mysql2 + sequelize

next

特殊的,在函数式组件中可以使用 awat 语句,在 return 方法前完成查询数据库等异步操作。

'use server' 代码在服务端执行,form 表单提交时可使用,方便处理数据,注意在 form 处理方法第一行加上。

const formAction = async (formData) => {
    'use server'
		// ...
    redirect('/');
  }

可以设置多个根目录表示不同页面,例如

app

(admin)

​ login

(display)

​ home

​ _header

()形式目录,不参与路径,但子目录可以继续参与。_name 自己和子目录均不参与路径

动态路由

以 [id] 形式作为文件名,从 page 页面中的 props 中可以取出 id 值。

中间件

可以在渲染组件前执行,做各类检查和补充信息的操作,例如查看登录重定向

export async function middleware(request) {
  // Getting cookies from the request using the `RequestCookies` API

  if(!cookie){
    return NextResponse.rewrite(new URL('/login', request.url))
  }

  return NextResponse.next()
}

若用户没有某些 cookie 信息,则重定向到登录页面。

MdEditor

动态引入,不让这个包影响首屏加载速度

const MdEditor = dynamic(
  () => import("@uiw/react-md-editor"),
  {ssr: false});

使用起来十分方便,使用 useState,将 value 和 setValue 绑定在 MdEditor 组件上即可

<MdEditor value={value} onChange={setValue} height={500}/>

其样式如下

image-20240330101919498

Markdown

使用 markdown 库可以直接展示 markdown 文件内容,但 react-markdown 并没有提供目录功能,为了优化体验,可以记录下 markdown 整篇文档中的标题数据,然后在侧边栏位置将其渲染出来。

import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import remarkToc from "remark-toc";
import rehypeRaw from "rehype-raw";
import rehypeKatex from "rehype-katex";
import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you
import Markdown from "react-markdown";
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'
import {prism} from 'react-syntax-highlighter/dist/esm/styles/prism'

<Markdown
          remarkPlugins={[remarkToc, remarkGfm, remarkMath]}
          rehypePlugins={[rehypeRaw, rehypeKatex]}
          children={md}
          components={{
            code(props) {
              const {children, className, node, ...rest} = props
              const match = /language-(\w+)/.exec(className || '');
              return match ? (
                <SyntaxHighlighter
                  {...rest}
                  PreTag="div"
                  children={String(children).replace(/\n$/, '')}
                  language={match[1]}
                  style={prism}
                />
              ) : (
                <code {...rest} className={`${className} code-quote`}>
                  {children}
                </code>
              )
            },
            h1: ({children}) => {
              console.error('xxx ', children)
              return <h1 id={id}>{children}</h1>
            },
            h2: ({children}) => {
              return <h2 className={'mark-down-page-h2'} id={id}>{children}</h2>
            },
            h3: ({children}) => {
              return <h3 id={id} className={'mark-down-page-h3'}>{children}</h3>
            },
            p: ({children}) => children === '[TOC]' ? null : (
              <p>{children}</p>
            ),
          }}
        />

效果如下

image-20240330103016367

数据库

第一步:连接数据库

数据使用的 mysql2

import {Sequelize} from "sequelize";
import mysqlDrive from 'mysql2';
require('dotenv').config();

export default new Sequelize('blog01', process.env.DB_USERNAME, process.env.DB_PASSWORD, {
  host: 'localhost',
  dialect: 'mysql',
  dialectModule: mysqlDrive,
});

第二步:定义数据结构

import {DataTypes} from "sequelize";
import sequelize from '@/app/_db/index'

export default sequelize.define('item_card', {
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
  },
  // xxx
}, {
  // 给表名默认加复数
  freezeTableName: true,
  // 列表名自动转驼峰
  underscored: true,
  // 禁止自动添加 created_at
  timestamps: false,
});

注意:

  1. sequelize 是第一步定义的连接数据库对象
  2. freezeTableName 设置为 true 才不会自动给表名添加 s 后缀
  3. underscored 设置为 true 列表名才不会自动转换为驼峰
  4. timestamps 设置为 false 才不会自动添加 created_at 列信息

第三步:查询语句

这里列举 crud 语句,其他查询可以在官网查看

// 查找符合条件的所有元素
export const getSearchKeyBlog = async (key) =>  {
  return moduleItemCard.findAll({
    limit: 10,
    order: [['create_time', 'DESC']],
    where: {
      class_type: 2,
      title: {
        [Op.like]: `%${key}%`
      }
    }
  });
}

// 查询唯一 id
export const findItemById = async (id) =>  {
  return moduleItemCard.findByPk(id);
}

// 更新内容
export const updateMd = async (id, data) =>  {
  return moduleItemCard.update(data, {
    where: {
      id,
    }
  });
}

// 插入内容
export const insertMd = async (values) =>  {
  return moduleItemCard.create(values);
}

frp 内网穿刺

官网:

第一步:安装

  1. 安装 systemd
# 使用 yum 安装 systemd(CentOS/RHEL)
yum install systemd

# 使用 apt 安装 systemd(Debian/Ubuntu)
apt install systemd

2. 安装 frps

  1. 创建 frps.service 文件,服务端配置

使用文本编辑器 (如 vim) 在 /etc/systemd/system 目录下创建一个 frps.service 文件,用于配置 frps 服务。

[Unit]
# 服务名称,可自定义
Description = frp server
After = network.target syslog.target
Wants = network.target

[Service]
Type = simple
# 启动frps的命令,需修改为您的frps的安装路径
ExecStart = /path/to/frps -c /path/to/frps.toml

[Install]
WantedBy = multi-user.target

4. 使用 systemd 命令管理 frps 服务

# 启动frp
sudo systemctl start frps
# 停止frp
sudo systemctl stop frps
# 重启frp
sudo systemctl restart frps
# 查看frp状态
sudo systemctl status frps

5. 设置 frps 开机自启动

sudo systemctl enable frps

第二步:配置服务端

在 fprs.toml 中更新配置

// 传输接口
bindPort = 7900
// http 端口
vhostHTTPPort = 80
// https 端口
vhostHTTPSPort = 443

# web 控制台端口
webServer.port = 7810
# dashboard 用户名密码,可选,默认为空
webServer.user = "xxx"
webServer.password = "xxx"
webServer.addr = "0.0.0.0"

后查看 web 页面可以看到总览,域名加 webServer.port 的域名

image-20240330105749041

第三步:配置客户端

和服务器端安装步骤一致,但是使用的安装文件为 trpc 文件。使用文本编辑器 (如 vim) 在 /etc/systemd/system 目录下创建一个 frpc.service 文件,用于配置 frpc 服务。

vscode 配置免密远程访问

第一步:下载插件

vscode 下载远程访问插件:Remote - SSH

第二步:配置 rsa 授权

查看 /.ssh/id_rsa.pub ,新增一行,复制 id_rsa.pub 内容到服务器的/.ssh/authorized_keys 中

第三步:连接远程地址

输入 ip+端口 连接远程地址,选中本机上 id_rsa.pub 存储地址

码云自动化部署

我们可以把仓库建在码云上,通过 webhooks 自动更新源码,自动打包,自动发布。具体流程如下

第一步:需要先在码云上创建自己的仓库

第二步:打开仓库管理,打开 webHooks 管理

第三步:在服务器上建立服务,接受来自码云的提交动作

第四步:添加 webhooks,在 push 动作时给服务器上发生 post 命令

第五步:服务器服务收到码云命令后,开始重新安装依赖,打包和发布