使用腾讯云函数服务运行 laravel 9 (黄了各位😅, 不免费了)
2022年4月23日18:42:56更新
刚才收到腾讯云 SCF
的短信提醒, 从 2022 年 5 月 23 日开始 SCF
不再提供免费服务了, 最低的套餐为 9.9元 每月, 这个费用已经超过了购买 VPS 的费用, 没想到文章才发三天就不免费了, 下面的文章大家可以忽略了, 附通告截图
摸鱼过程中偶然发现了腾讯云出了 SCF
函数服务(可能早就有了,但我不知道),经过一些研究探索,成功运行了
laravel
框架,搭配 Api网关服务
、TDSQL-C数据库
,几乎可实现免费搭建小型网站
1.创建项目代码
使用 Composer 安装
composer create-project laravel/laravel
因云函数服务不支持在项目路径写入文件,故将各处写入文件定位至 /tmp
,编辑 .env
文件,在底部新增
# 设置模板缓存路径
VIEW_COMPILED_PATH=/tmp
# 设置应用缓存路径
APP_STORAGE=/tmp
# 设置日志输出至 stderr
LOG_CHANNEL=stderr
# 设置 session 以内存形式储存 或自行修改使用 mysql 储存
SESSION_DRIVER=array
在项目根目录下创建处理文件 handler.php
,内容如下
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\HeaderBag;
define('LARAVEL_START', microtime(true));
define('TEXT_REG', '#\.html.*|\.js.*|\.css.*|\.html.*#');
define('BINARY_REG', '#\.ttf.*|\.woff.*|\.woff2.*|\.gif.*|\.jpg.*|\.png.*|\.jepg.*|\.swf.*|\.bmp.*|\.ico.*#');
/**
* 静态文件处理
*/
function handlerStatic($path, $isBase64Encoded)
{
$filename = __DIR__ . "/public" . $path;
if (!file_exists($filename)) {
return [
"isBase64Encoded" => false,
"statusCode" => 404,
"headers" => [
'Content-Type' => '',
],
"body" => "404 Not Found",
];
}
$handle = fopen($filename, "r");
$contents = fread($handle, filesize($filename));
fclose($handle);
$base64Encode = false;
$headers = [
'Content-Type' => '',
'Cache-Control' => "max-age=8640000",
'Accept-Ranges' => 'bytes',
];
$body = $contents;
if ($isBase64Encoded || preg_match(BINARY_REG, $path)) {
$base64Encode = true;
$headers = [
'Content-Type' => '',
'Cache-Control' => "max-age=86400",
];
$body = base64_encode($contents);
}
return [
"isBase64Encoded" => $base64Encode,
"statusCode" => 200,
"headers" => $headers,
"body" => $body,
];
}
function initEnvironment($isBase64Encoded)
{
$envName = '';
if (file_exists(__DIR__ . "/.env")) {
$envName = '.env';
} elseif (file_exists(__DIR__ . "/.env.production")) {
$envName = '.env.production';
} elseif (file_exists(__DIR__ . "/.env.local")) {
$envName = ".env.local";
}
if (!$envName) {
return [
'isBase64Encoded' => $isBase64Encoded,
'statusCode' => 500,
'headers' => [
'Content-Type' => 'application/json'
],
'body' => $isBase64Encoded ? base64_encode([
'error' => "Dotenv config file not exist"
]) : [
'error' => "Dotenv config file not exist"
]
];
}
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__, $envName);
$dotenv->load();
}
function decodeFormData($rawData)
{
$files = array();
$data = array();
$boundary = substr($rawData, 0, strpos($rawData, "\r\n"));
$parts = array_slice(explode($boundary, $rawData), 1);
foreach ($parts as $part) {
if ($part == "--\r\n") {
break;
}
$part = ltrim($part, "\r\n");
list($rawHeaders, $content) = explode("\r\n\r\n", $part, 2);
$content = substr($content, 0, strlen($content) - 2);
// 获取请求头信息
$rawHeaders = explode("\r\n", $rawHeaders);
$headers = array();
foreach ($rawHeaders as $header) {
list($name, $value) = explode(':', $header);
$headers[strtolower($name)] = ltrim($value, ' ');
}
if (isset($headers['content-disposition'])) {
$filename = null;
preg_match('/^form-data; *name="([^"]+)"(; *filename="([^"]+)")?/', $headers['content-disposition'], $matches);
$fieldName = $matches[1];
$fileName = (isset($matches[3]) ? $matches[3] : null);
// If we have a file, save it. Otherwise, save the data.
if ($fileName !== null) {
$localFileName = tempnam('/tmp', 'sls');
file_put_contents($localFileName, $content);
$arr = array(
'name' => $fileName,
'type' => $headers['content-type'],
'tmp_name' => $localFileName,
'error' => 0,
'size' => filesize($localFileName)
);
if (substr($fieldName, -2, 2) == '[]') {
$fieldName = substr($fieldName, 0, strlen($fieldName) - 2);
}
if (array_key_exists($fieldName, $files)) {
array_push($files[$fieldName], $arr);
} else {
$files[$fieldName] = $arr;
}
// register a shutdown function to cleanup the temporary file
register_shutdown_function(function () use ($localFileName) {
unlink($localFileName);
});
} else {
parse_str($fieldName . '=__INPUT__', $parsedInput);
$dottedInput = arrayDot($parsedInput);
$targetInput = arrayAdd([], array_keys($dottedInput)[0], $content);
$data = array_merge_recursive($data, $targetInput);
}
}
}
return (object)([
'data' => $data,
'files' => $files
]);
}
function arrayGet($array, $key, $default = null)
{
if (is_null($key)) {
return $array;
}
if (array_key_exists($key, $array)) {
return $array[$key];
}
if (strpos($key, '.') === false) {
return $array[$key] ?? value($default);
}
foreach (explode('.', $key) as $segment) {
$array = $array[$segment];
}
return $array;
}
function arrayAdd($array, $key, $value)
{
if (is_null(arrayGet($array, $key))) {
arraySet($array, $key, $value);
}
return $array;
}
function arraySet(&$array, $key, $value)
{
if (is_null($key)) {
return $array = $value;
}
$keys = explode('.', $key);
foreach ($keys as $i => $key) {
if (count($keys) === 1) {
break;
}
unset($keys[$i]);
if (!isset($array[$key]) || !is_array($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
function arrayDot($array, $prepend = '')
{
$results = [];
foreach ($array as $key => $value) {
if (is_array($value) && !empty($value)) {
$results = array_merge($results, dot($value, $prepend . $key . '.'));
} else {
$results[$prepend . $key] = $value;
}
}
return $results;
}
function getHeadersContentType($headers)
{
if (isset($headers['Content-Type'])) {
return $headers['Content-Type'];
} else if (isset($headers['content-type'])) {
return $headers['content-type'];
}
return '';
}
function handler($event, $context)
{
require __DIR__ . '/vendor/autoload.php';
$isBase64Encoded = $event->isBase64Encoded;
initEnvironment($isBase64Encoded);
$app = require __DIR__ . '/bootstrap/app.php';
// change storage path to APP_STORAGE in dotenv
$app->useStoragePath(env('APP_STORAGE', base_path() . '/storage'));
// 获取请求路径
$path = str_replace("//", "/", $event->path);
if (preg_match(TEXT_REG, $path) || preg_match(BINARY_REG, $path)) {
return handlerStatic($path, $isBase64Encoded);
}
// 处理请求头
$headers = $event->headers ?? [];
$headers = json_decode(json_encode($headers), true);
// 处理请求数据
$data = [];
$rawBody = $event->body ?? null;
if ($event->httpMethod === 'GET') {
$data = !empty($event->queryString) ? $event->queryString : [];
} else {
if ($isBase64Encoded) {
$rawBody = base64_decode($rawBody);
}
$contentType = getHeadersContentType($headers);
if (preg_match('/multipart\/form-data/', $contentType)) {
$requestData = !empty($rawBody) ? decodeFormData($rawBody) : [];
$data = $requestData->data;
$files = $requestData->files;
} else if (preg_match('/application\/x-www-form-urlencoded/', $contentType)) {
if (!empty($rawBody)) {
mb_parse_str($rawBody, $data);
}
} else {
$data = !empty($rawBody) ? json_decode($rawBody, true) : [];
}
}
// 将请求交给 laravel 处理
$kernel = $app->make(Kernel::class);
var_dump($path, $event->httpMethod);
$request = Request::create($path, $event->httpMethod, (array)$data, [], [], $headers, $rawBody);
$request->headers = new HeaderBag($headers);
if (!empty($files)) {
$request->files->add($files);
}
$response = $kernel->handle($request);
// 处理返回内容
$body = $response->getContent();
$headers = $response->headers->all();
$response_headers = [];
foreach ($headers as $k => $header) {
if (is_string($header)) {
$response_headers[$k] = $header;
} elseif (is_array($header)) {
$response_headers[$k] = implode(';', $header);
}
}
return [
'isBase64Encoded' => $isBase64Encoded,
'statusCode' => $response->getStatusCode() ?? 200,
'headers' => $response_headers,
'body' => $isBase64Encoded ? base64_encode($body) : $body
];
}
2.创建 SCF
函数
- 登录腾讯云控制台,搜索并打开
函数服务
- 点击新建,选择从头开始,函数类型选择 事件函数 ,运行环境选择
php 8.0
- 使用本地上传 zip 包方式,将本地代码上传,执行方法填写
handler.handler
,即上文创建的handler.php
中的handler
方法
下方高级配置按需要调整即可,(注意:函数运行日志会记录到腾讯云 CLS
,该服务为收费服务,有免费额度,具体可参考配置页面下方说明),填写完成后点击完成生成函数
3.创建 api 网关服务
该服务月调用量小于等于 100 万次不收费,超过 100 万次后,每万次 0.06 元
- 打开腾讯云控制台, 搜索并进入
Api 网关
控制台,点击新建, 共享性, 直接提交即可 - 在刚创建的
Api 网关
上点击配置管理, 点击管理 Api ,点击新建 - API名称随意填写, 路径填写
/
, 请求方法选择 any , 点击下一步 - 在后端配置中选择 云函数SCF ,选择刚创建的函数,并勾选响应集成
- 点击下一步,直接提交保存即可,根据提示点击立即发布
- 点击基础配置,复制访问地址
- 浏览器打开测试,返回如图
- 根据个人需要在 自定义域名 中绑定个人域名即可
创建 TDSQL-C数据库
TDSQL-C
100% 兼容 mysql
, 该服务可选择按量付费, 不使用不计费模式
- 打开腾讯云控制台,搜索并进入 TDSQL-C MySQL 版
- 点击新建,计费方式选择 Serverless, 之后根据个人需要调整各项配置即可
提示
- 该服务无法使用文件方式储存 session,可选择使用 redis 或 mysql 储存 session
- 由于 api 网关限制,上传文件最大支持 2M,如有上传需求,需在创建 api 网关勾选 base64 编码,建议使用第三方储存
- 如不需函数运行日志,可在
CLS
控制台直接删除系统创建的日志集
另外本人准备搭建一个个人博客网站,用于记录日常,有没有小伙伴的博客可以借(抄)鉴(袭)一下
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: