经历千辛万苦,我终于安置好了个人站。

开整前的胡扯

以前个人站在腾讯云的孟买轻量应用服务器上,访问速度感人。后来就将他合并进主站所在的北京应用服务器,但在国内的服务器提供网站服务都需要备案。个人站因为域名原因没法备案,所以大部分地区都会被屏蔽。后来我也动过租一个香港节点服务器的念头,但是看到恐怖的单价,我这个穷学生只得望而却步。

这可是起步价
这可是起步价

前几天和朋友聊到了ipfs,和他分享了imalan的静态博客生成器。闲聊之余,我也对Vercel的静态网站托管服务起了兴趣。在简单了解后我得知Vercel可以托管PHP网站,便尝试将自己的typecho博客部署上去。然而在构建时生成的api.php超过了最大限制(50MB),我也只好作罢……

当然不可能。Vercel勾起了我对Serverless的好奇。经过一番研究后,我选择在腾讯云的SCF部署一个Typecho博客。

开整准备

  1. typecho本体(目前最新版本为1.2.1,经体验似乎没出现与1.2.0插件/主题不兼容的情况)
  2. Apache/Nginx(十分建议,不过非必选,用于在本地进行修改后的预览以及功能测试)

开整

部署前的准备

首先在腾讯云新建一个TDSQL-C数据库。建立数据库的详细教程不再给出,这里仅记录本例子的配置。实例形态选择Serverless,地域选择自己之后准备部署博客的地域(这里以广州为例),新建一个私有网络或使用默认,数据库版本选择MySQL8.0。算力配置和自动暂停用默认配置,默认字符集为UTF8MB4,表名大小写不敏感。

创建完成后进入集群详情,开启外网地址。

登录刚才创建的数据库,新建一个数据库,字符集使用utf8mb4,名称随意,如图本示例使用“example”。

数据库配置
数据库配置

如果你的typecho是从其他服务器迁移的(根目录下有config.inc.php),则可跳过第一次运行生成config.inc.php的步骤,直接转到创建scf_boostrap处。

将获得的Typecho本体部署到本地的Apache或Nginx上,进行第一次运行。关于如何将Typecho部署在web服务器上,网上教程漫天飞,本文不再赘述。

在第一次部署后,访问localhost会自动跳转到install.php,引导用户进行初始化。

初始化配置阶段,我们要将刚刚新建的数据库地址,端口(在高级选项里),数据库名,用户名和密码填进去。数据库适配器选择Pdo驱动Mysql适配器,数据库前缀自行设置,这里展示本例子的配置。

初始化
初始化

然后设置管理员账户后,typecho的初次设置就完成了,此时访问localhost,则会正常进入首页如图。

平平无奇的首页
平平无奇的首页

此时再检查typecho的项目结构,会发现根目录下多了一个文件:config.inc.php。打开该文件,可以看到刚才的配置信息都已记录到该文件中,此处展示本示例的config.inc.php

<?php
// site root path
define('__TYPECHO_ROOT_DIR__', dirname(__FILE__));

// plugin directory (relative path)
define('__TYPECHO_PLUGIN_DIR__', '/usr/plugins');

// theme directory (relative path)
define('__TYPECHO_THEME_DIR__', '/usr/themes');

// admin directory (relative path)
define('__TYPECHO_ADMIN_DIR__', '/admin/');

// register autoload
require_once __TYPECHO_ROOT_DIR__ . '/var/Typecho/Common.php';

// init
\Typecho\Common::init();

// config db
$db = new \Typecho\Db('Pdo_Mysql', 'typecho_');
$db->addServer(array (
  'host' => '***', //你的数据库地址
  'port' => 22957, //数据库端口
  'user' => 'root', //数据库用户名称
  'password' => '***', //数据库用户密码
  'charset' => 'utf8mb4', //数据库字符集
  'database' => 'example', //数据库名称
  'engine' => 'InnoDB',
  'sslCa' => '',
  'sslVerify' => true,
), \Typecho\Db::READ | \Typecho\Db::WRITE);
\Typecho\Db::set($db);

为了让他可以部署到SCF,我们要加入scf_bootstrap文件。这里先以SCF文档中展示的最简单的启动文件为例:

#!/bin/bash
/var/lang/php7/bin/php -c /var/runtime/php7 -S 0.0.0.0:9000 index.php

这时的项目结构应如下:

.
├─admin/
├─install/
├─usr/
├─var/
├─config.inc.php
├─index.php
├─install.php
├─LISENCE.txt
└─scf-boostrap

这样我们就做好了部署前的准备。

部署函数

进入serverless-函数服务,点击新建,选择“从头开始”。函数类型为Web函数,函数名称自己起。

地域选择与数据库相同的地域,运行环境为Php 7.4,时区为Asia/Shanghai(北京时间)。

将准备好的Typecho文件夹上传。这里展示本示例的配置。

如果地域选择国内,那么自定义域名阶段则要备案
如果地域选择国内,那么自定义域名阶段则要备案

进入高级配置,描述按自己喜好修改,启动命令不用管,腾讯云会优先使用项目文件中的scf_bootstrap文件。

内存初始化超时时间按自己需求修改,执行超时时间建议不要低于五秒。

在网络配置中启用私有网络,选择和数据库相同的私有网络和子网。

完成以上配置后点击完成,函数就会开始部署。部署完成后进入触发管理。点击“创建触发器”,点击提交,就可以得到公网的访问路径。点击后出现Typecho主页,初步配置就完成了。

设置数据库从内网访问

在上一步中的网络配置中正确设置了私有网路,那么我们就可以通过内网访问数据库,提升访问速度和安全性。

进入函数管理,点击函数代码,打开config.inc.php,修改其中有关数据库的内容。将数据库地址改为其内网地址,端口改为3306,其余内容不需要变动,点击部署即可。

其实我不是很适应在线编辑
其实我不是很适应在线编辑

部署成功后,点击下方的访问路径,可以正常访问,则表示配置成功,记得将数据库的外网地址关闭。

配置handler.php

此时我们点击Typecho主页的登录,会发现仍然会跳转到主页,无法正常访问后台。我们需要修改逻辑,使得可以访问其他PHP文件。

这里我们可以借鉴 Serverless Cloud Framework WordPress 组件,参考其handle.php的内容。以下直接给出解决步骤:

src目录下新建handle.php,并输入以下内容:

<?php
error_reporting(0);

$extension_map = array(
    "css" => "text/css",
    "js" => "application/javascript",
    "png" => "image/png",
    "jpeg" => "image/jpeg",
    "jpg" => "application/x-jpg",
    "svg" => "image/svg+xml",
    "gif" => "image/gif",
    "pdf" => "application/pdf",
    "mp4" => "video/mpeg4",
    "bmp" => "application/x-bmp",
    "c4t" => "application/x-c4t",
    "img" => "application/x-img",
    "m2v" => "video/x-mpeg",
    "mp2v" => "video/mpeg",
    "mpeg" => "video/mpg",
    "ppt" => "application/x-ppt",
    "rm" => "application/vnd.rn-realmedia",
    "swf" => "video/mpeg4",
    "tif" => "image/tiff",
    "tiff" => "image/tiff",
    "ttf" => "application/x-font-ttf",
    "woff" => "application/x-font-woff",
    "vcf" => "text/x-vcard",
    "wav" => "audio/wav",
    "wma" => "audio/x-ms-wma",
    "wmv" => "video/x-ms-wmv",
    "apk" => "application/vnd.android.package-archive",
    "m1v" => "video/x-mpeg",
    "m3u" => "audio/mpegurl",
    "mp2" => "audio/mp2",
    "mp3" => "audio/mp3",
    "mpa" => "video/x-mpg",
    "mpe" => "video/x-mpeg",
    "mpg" => "video/mpg",
    "mpv2" => "video/mpeg",
    "rmvb" => "application/vnd.rn-realmedia-vbr",
    "torrent" => "application/x-bittorrent",
);

$request_uri = explode("?", $_SERVER['REQUEST_URI']);
$local_file_path = $_SERVER['DOCUMENT_ROOT'] . urldecode($request_uri[0]);
$remote_file_path = "/mnt/" . urldecode($request_uri[0]);


if ( $local_file_path == __FILE__ ) {
    http_response_code(400);
    echo 'Sorry';
    exit();
}


$db_mode = "";
$db_mode = getenv("DB_MODE");
$cdb_st = microtime(true);
$split = explode(".", $local_file_path);
$extension = end($split);
$mapped_type = $extension_map[$extension];

if ( $mapped_type && file_exists( $remote_file_path ) ) {

    header("Content-Type: {$mapped_type}");
    $file_size=filesize($remote_file_path);
    header("Accept-Length:$file_size");
    $fp=fopen($remote_file_path,"r");
    $buffer=1024;
    $file_count=0;
    while(!feof($fp)&&($file_size-$file_count>0)){
        $file_data=fread($fp,$buffer);
        $file_count+=$buffer;
        echo $file_data;
    }
    fclose($fp);
} elseif ( $mapped_type && file_exists( $local_file_path ) ) {

    header("Content-Type: {$mapped_type}");
    $file_size=filesize($local_file_path);
    header("Accept-Length:$file_size");
    $fp=fopen($local_file_path,"r");
    $buffer=1024;
    $file_count=0;
    while(!feof($fp)&&($file_size-$file_count>0)){
        $file_data=fread($fp,$buffer);
        $file_count+=$buffer;
        echo $file_data;
    }
    fclose($fp);

} elseif ( $extension == "php" && file_exists( $local_file_path ) ) {

    $cdb_et = microtime(true);
    $cdb_time = ($cdb_et - $cdb_st) * 1000 . 'ms';
    header("X-cdb-time:{$cdb_time}");
    header('Cache-Control:no-cache,must-revalidate');
    header('Pragma:no-cache');
    header("Expires:0");
    header("X-ExecFile: {$local_file_path}");
    require( $local_file_path );

} elseif ( substr($local_file_path, -1) == "/" && file_exists( $local_file_path . "index.php" ) ) {
    $cdb_et = microtime(true);
    $cdb_time = ($cdb_et - $cdb_st) * 1000 . 'ms';
    header("X-cdb-time:{$cdb_time}");
    header('Cache-Control:no-cache,must-revalidate');
    header('Pragma:no-cache');
    header("Expires:0");
    $exec_file_path = $local_file_path . "index.php";
    header("X-ExecFile: {$exec_file_path}");
    require( $exec_file_path );
} else {
    $cdb_et = microtime(true);
    $cdb_time = ($cdb_et - $cdb_st) * 1000 . 'ms';
    header("X-cdb-time:{$cdb_time}");
    header('Cache-Control:no-cache,must-revalidate');
    header('Pragma:no-cache');
    header("Expires:0");
    $exec_file_path = dirname(__FILE__) . '/index.php';
    header("X-ExecFile: {$exec_file_path}");
    require( $exec_file_path );
}

然后修改scf_bootstrap文件:

#!/bin/bash
/var/lang/php7/bin/php -c /var/runtime/php7 -S 0.0.0.0:9000 handle.php

此时的项目结构应当如下:

.
├─admin/
├─install/
├─usr/
├─var/
├─config.inc.php
├─handle.php
├─index.php
├─install.php
├─LISENCE.txt
└─scf-boostrap

完成后将其部署,就可以正常登录和访问后台了。

自定义域名

如果需要自定义域名,则需将触发器升级为API网关标准版。

进入触发管理,点击”升级至API网关标准版“。

没啥好说的,如图
没啥好说的,如图

点击你的API服务名,进入API网关控制台。进入自定义域名,将自己的域名进行配置,详细步骤不再展示。

补充内容

  1. 需要新增插件或主题时,只需将其上传到src/usr下的对应目录即可。
  2. 部署在SCF的Typecho将无法正常使用上传功能。这里给出两个方法解决:

    一,将需要上传的图片/文件保存在图床,然后在博文中引用。

    二,使用COS插件,填补博客的上传功能。我使用的是:腾讯云对象存储(COS)插件。设置时需关闭“在本地保存”功能。

整完后的胡扯

说实话,作为一介穷学生,以前几乎是没接触过云原生的,顶多用用docker。这次也算是摸到了云原生的一点边,确实是次十分新奇的体验。本文是咱第一次写这种技术教程,主要动机是“阿里云都有部署typecho的教程和模板,腾讯云居然一点内容都没有”,遂在折腾完后扯了这么一篇。没什么技术含量,甚至很多步骤都是“手把手”级别,希望能帮到其他初试serverless的人。如果本文有错漏或是可以改进之处,也希望得到各位大佬斧正,十分感谢。